From 07b2638cd4a27e1b04e6b7baca3d9615978d2698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E5=A4=8F=E6=B5=85=E7=AC=91?= Date: Fri, 21 Jan 2022 23:53:12 +0800 Subject: [PATCH] Init Project --- .gitignore | 3 + README.md | 9 +++ go.mod | 7 ++ go.sum | 15 ++++ main.go | 181 +++++++++++++++++++++++++++++++++++++++++++++ target/golist.json | 1 + 6 files changed, 216 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 target/golist.json diff --git a/.gitignore b/.gitignore index f1c181e..2ac5bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +.vscode +goproject.code-workspace +helloworld.go.bak diff --git a/README.md b/README.md index c7eb0da..a03e5dd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # acmeDeliver + acme.sh 证书分发服务 + +将 acme.sh 获取的证书通过 http 服务分发到多台服务器 + +## Usage + +```bash +./acmeDeliver +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..65a550f --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/julydate/acmeDeliver + +go 1.17 + +require github.com/thinkeridea/go-extend v1.3.2 + +require github.com/zekroTJA/timedmap v1.4.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3a84dea --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/thinkeridea/go-extend v1.3.2 h1:0ZImRXpJc+wBNIrNEMbTuKwIvJ6eFoeuNAewvzONrI0= +github.com/thinkeridea/go-extend v1.3.2/go.mod h1:xqN1e3y1PdVSij1VZp6iPKlO8I4jLbS8CUuTySj981g= +github.com/zekroTJA/timedmap v1.4.0 h1:NIkLScX6kMzkzFP7kCIkkgKYdooAJ1itkMbJODX2WPU= +github.com/zekroTJA/timedmap v1.4.0/go.mod h1:Go4uPxMN1Wjl5IgO6HYD1tM9IQhkYEVqcrrdsI4ljXo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9995e6f --- /dev/null +++ b/main.go @@ -0,0 +1,181 @@ +package main + +import ( + "crypto/md5" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "strconv" + "time" + + "github.com/thinkeridea/go-extend/exnet" + "github.com/zekroTJA/timedmap" +) + +// PORT 服务端口 +const PORT string = "9090" + +// DIR 证书文件所在目录 +const DIR string = "./" + +// KEY 密码 +const KEY string = "passwd" + +// EXPTIME 时间戳误差,单位秒 +const EXPTIME int64 = 86400 + +// 初始化全局变量 +var domain, file, t, checksum, sign string + +// Creates a new timed map which scans for expired keys every 1 second +var tm = timedmap.New(1 * time.Second) + +func main() { + // 设置访问的路由 + http.HandleFunc("/", check) + // 设置监听的端口 + err := http.ListenAndServe(":"+PORT, nil) + if err != nil { + log.Fatal("ListenAndServe:", err) + } +} + +func check(response http.ResponseWriter, req *http.Request) { + // 解析 url 传递的参数,对于 POST 则解析响应包的主体(request body) + err := req.ParseForm() + if err != nil { + log.Fatal("ParseForm:", err) + return + } + + // 获取来访 IP 地址 + var ip = exnet.ClientPublicIP(req) + if ip == "" { + ip = exnet.ClientIP(req) + } + + // 获取传入域名 + if len(req.Form.Get("domain")) == 0 { + fmt.Fprintf(response, "No domain specified.") + return + } + domain = req.Form.Get("domain") + // 获取传入文件名 + if len(req.Form.Get("file")) == 0 { + fmt.Fprintf(response, "No file specified.") + return + } + file = req.Form.Get("file") + // 获取传入签名 + if len(req.Form.Get("sign")) == 0 { + fmt.Fprintf(response, "No sign specified.") + return + } + sign = req.Form.Get("sign") + // 获取传入验证码 + if len(req.Form.Get("checksum")) == 0 { + fmt.Fprintf(response, "No checksum specified.") + return + } + checksum = req.Form.Get("checksum") + // 获取传入时间戳 + if len(req.Form.Get("t")) == 0 { + fmt.Fprintf(response, "No timestamp specified.") + return + } + t = req.Form.Get("t") + + // 校验时间戳是否合法 + reqTime, err := strconv.ParseInt(t, 10, 64) + if err != nil { + fmt.Println("Access from IP:", ip) + fmt.Println("Incoming illegal timestamp:", t) + fmt.Fprintf(response, "Timestamp not allowed.") + return + } + expireTime := time.Now().Unix() - reqTime + // 时间戳太超前可以判定为异常访问 + if expireTime < -EXPTIME { + fmt.Println("Access from IP:", ip) + fmt.Println("Incoming illegal timestamp:", expireTime) + fmt.Fprintf(response, "Timestamp not allowed.") + return + } + // 校验时间戳是否过期 + if expireTime > EXPTIME { + fmt.Println("Access from IP:", ip) + fmt.Println("Incoming expired access:", expireTime) + fmt.Fprintf(response, "Timestamp expired.") + return + } + + // 计算 token + token := md5.New() + io.WriteString(token, domain) + io.WriteString(token, file) + io.WriteString(token, KEY) + io.WriteString(token, t) + io.WriteString(token, checksum) + checkToken := fmt.Sprintf("%x", token.Sum(nil)) + + // 校验签名 + if sign == checkToken { + // 检测验证码是否重复 + if checkTime, ok := tm.GetValue(checksum).(int64); ok { + if checkTime > 0 && time.Now().Unix()-checkTime > EXPTIME { + tm.Remove(checkTime) + } else { + // 检测到重放请求 + fmt.Println("Access from IP:", ip) + fmt.Println("Incoming repeat access:", checksum) + fmt.Fprintf(response, "Repeat access.") + return + } + } else { + tm.Set(checksum, reqTime, time.Duration(EXPTIME)*time.Second) + } + + // 校验域名是否在指定文件夹内 + var checkDomain, checkFile bool = false, false + files, _ := ioutil.ReadDir(DIR) + for _, f := range files { + if domain == f.Name() { + checkDomain = true + } + } + if checkDomain { + // 对应域名的文件夹存在,校验内部文件是否存在 + files, _ := ioutil.ReadDir(DIR + domain) + for _, f := range files { + if file == f.Name() { + checkFile = true + } + } + } else { + // 获取的域名不存在 + fmt.Println("Access from IP:", ip) + fmt.Println("Incoming illegal domain:", domain) + fmt.Fprintf(response, "Domain not exist.") + return + } + if !checkFile { + // 获取的文件不存在 + fmt.Println("Access from IP:", ip) + fmt.Println("Incoming illegal filename:", file) + fmt.Fprintf(response, "File not exist.") + return + } + // 全部校验通过,放行文件 + filepath := DIR + domain + "/" + file + fmt.Println("Access from IP:", ip) + fmt.Println("Access file:", filepath) + http.ServeFile(response, req, filepath) + } else { + // 签名错误 + fmt.Println("Access from IP:", ip) + fmt.Println("Incoming unauthorized access:", sign) + fmt.Fprintf(response, "Unauthorized access.") + } +} diff --git a/target/golist.json b/target/golist.json new file mode 100644 index 0000000..d1e72bc --- /dev/null +++ b/target/golist.json @@ -0,0 +1 @@ +{"version":"v1","main":"acmeDeliver","packages":[{"package":"github.com/thinkeridea/go-extend/exnet@github.com/thinkeridea/go-extend","version":"1.3.2"}]} \ No newline at end of file