3

为Redis编写安全通道

 2 years ago
source link: https://allenwind.github.io/blog/5853/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Mr.Feng Blog

NLP、深度学习、机器学习、Python、Go

为Redis编写安全通道

最近需要用到服务器的Redis数据库,但又不想让数据库的监听端口暴露到外网,另外Redis本身不支持数据加密。还有什么方法让本地客户端连上服务器的数据库呢?

答案是使用代理。接下来的实现使用Go语言,由于它的静态连接特性,编译后放到服务器即可。它的思路很简单,把本地客户端的数据加密后送到服务器,服务器接收后解密转发到本地指定的端口。

client --> localproxy --加密的数据--> remoteproxy -转发到-> Redis
127.0.0.1:7744 remoteAddr:443 127.0.0.1:6379

如果client支持tls可以把localproxy这过程免去,即

tlsclient --加密的数据--> remoteproxy -转发到-> Redis
port:443 port:6379

下面的是TCP代理的实现,交叉编译后部署到Linux服务器后运行即可。

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") // connect to Redis
if err != nil {
log.Println(err)
continue
}
go io.Copy(db, client) // copy client data to database
io.Copy(client, db) // copy database data to client
}
}

TCP本身并不安全,我们需要在TCP上添加一层加密SSL。可以通过openssl创建服务器的证书和密钥。

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") // connect to Redis
if err != nil {
log.Println(err)
return
}
go io.Copy(remote, client) // copy client data to database
io.Copy(client, remote) // copy database data to client
}

客户端代码如下:

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,还适用于MongoDBMySQL,不过后两者就已经有SSL功能。从官方文档看,Redis推荐使用 spipe 作数据加密。另外,利用类似的方法不难让数据库走socks5代理,以后有空写写。

除此之外,还可以为代理添加其他功能:为多数据库作负载均衡、读写分离。不过数据加密解密和端口转发还是有一定的延时。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK