最近需要用到服务器的Redis
数据库,但又不想让数据库的监听端口暴露到外网,另外Redis本身不支持数据加密。还有什么方法让本地客户端连上服务器的数据库呢?
答案是使用代理。接下来的实现使用Go
语言,由于它的静态连接特性,编译后放到服务器即可。它的思路很简单,把本地客户端的数据加密后送到服务器,服务器接收后解密转发到本地指定的端口。
1 2
| client --> localproxy --加密的数据--> remoteproxy -转发到-> Redis 127.0.0.1:7744 remoteAddr:443 127.0.0.1:6379
|
如果client
支持tls
可以把localproxy
这过程免去,即
1 2
| tlsclient --加密的数据--> remoteproxy -转发到-> Redis port:443 port:6379
|
下面的是TCP
代理的实现,交叉编译后部署到Linux
服务器后运行即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package main
import ( "io" "log" "net" )
func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) l, err := net.Listen("tcp", ":62815") if err != nil { log.Panic(err) } for { client, err := l.Accept() if err != nil { log.Println(err) continue } db, err := net.Dial("tcp", "localhost:6379") if err != nil { log.Println(err) continue } go io.Copy(db, client) io.Copy(client, db) } }
|
但TCP
本身并不安全,我们需要在TCP
上添加一层加密SSL
。可以通过openssl
创建服务器的证书和密钥。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package main
import ( "crypto/tls" "io" "log" "net" )
func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) cer, err := tls.LoadX509KeyPair("server.crt", "server.key") if err != nil { log.Fatal(err) } config := &tls.Config{Certificates: []tls.Certificate{cer}}
l, err := tls.Listen("tcp", ":62815", config) defer l.Close() if err != nil { log.Panic(err) }
for { client, err := l.Accept() if err != nil { log.Println(err) continue } go handleTCPRequest(client) } }
func handleTCPRequest(client net.Conn) { remote, err := net.Dial("tcp", "localhost:6379") if err != nil { log.Println(err) return } go io.Copy(remote, client) io.Copy(client, remote) }
|
客户端代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package main
import ( "crypto/tls" "io" "log" "net" )
func main() { log.SetFlags(log.LstdFlags | log.Lshortfile)
local, err := net.Listen("tcp", ":7744") defer local.Close() if err != nil { log.Fatal(err) }
for { client, err := local.Accept() if err != nil { log.Println(err) continue } go handleTLSRequest(client)
} }
func handleTLSRequest(client net.Conn) { conf := &tls.Config{} remote, err := tls.Dial("tcp", "localhost:443", conf) defer remote.Close() if err != nil { log.Fatal(err) }
go io.Copy(remote, client) io.Copy(client, remote) }
|
上面的代码整合下写成命令行工具即可方便使用。如果client
支持tls
可以把localproxy
这过程免去。
事实上,这种方法不仅适用于Redis
,还适用于MongoDB
、MySQL
,不过后两者就已经有SSL
功能。从官方文档看,Redis
推荐使用 spipe 作数据加密。另外,利用类似的方法不难让数据库走socks5
代理,以后有空写写。
除此之外,还可以为代理添加其他功能:为多数据库作负载均衡、读写分离。不过数据加密解密和端口转发还是有一定的延时。