英文:
What mTLS certificates are required when the client is also the server?
问题
我正在寻找一种在golang中使用mTLS连接服务器和多个客户端的方法。在我的服务器上,我希望能够生成证书并将其放在所有客户端上,以便客户端可以与服务器通信,但客户端之间不能相互通信。然而,我的客户端在端口80上公开了golang http服务器,我的服务器通过API请求与客户端通信,而不是反过来(从技术上讲,你可以质疑我所称的服务器是否实际上是客户端,但它是生成证书的单一来源,并为客户端提供内容,因此为简单起见,将坚持使用这个命名)。如何设置这个?需要生成哪些证书?是否可以让客户端使用从单个服务器生成的证书来监听请求?
我很可能会使用SmallStep来生成证书,SmallStep的示例会很有用,但一般的方法也可以,然后可以单独查看如何使用SmallStep复制它。
我已经查看了一些现有的golang示例,但它们往往针对不同的设置:
https://smallstep.com/hello-mtls/doc/server/go
https://venilnoronha.io/a-step-by-step-guide-to-mtls-in-go
以下是目前的代码,我相当确定它在错误地使用证书:
客户端:
step ca certificate "localhost" client.crt client.key
在下面的代码中,cert和key变量分别是client.crt和client.key。
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM([]byte(cert))
// 读取密钥对以创建证书
keyPair, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err != nil {
log.Fatal(err)
}
transport = &http.Transport{
IdleConnTimeout: transportIdleConnTimeout,
MaxIdleConnsPerHost: transportMaxIdleConnsPerHost,
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{keyPair},
},
}
服务器:
step ca certificate "pm1" server.crt server.key
在下面的代码中,cert和key变量分别是server.crt和server.key。
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM([]byte(cert))
cert, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err != nil {
log.Fatal("server: loadkeys: ", err)
}
tlsConfig := tls.Config{
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}
我现在完全没有使用ca.crt(step ca root ca.crt
)。
英文:
I am looking to connect a server and a number of clients using mTLS in golang. On my server I would like to be able to generate certificates to put on all the clients so clients can talk to the server but clients cannot talk to each other. My clients however, expose the golang http server on port 80 and my server communicates with the clients through API requests rather than the other way around (technically you could question whether what I am calling a server is in fact a client, but it’s a single source for certificate generation and serves content to the clients so will stick with this naming for simplicity). How could this be set up? Which certificates would need to be generated and is it possible to have the clients listen for requests using certificates generated from a single server?
I will most likely be using SmallStep for certificate generation and SmallStep examples would be useful, but the general approach would be fine too and can then look at how to replicate it with SmallStep separately.
I have looked at some existing golang examples but they tend to be steered at different setups:
https://smallstep.com/hello-mtls/doc/server/go
https://venilnoronha.io/a-step-by-step-guide-to-mtls-in-go
Here is the code at the moment which I am quite sure is using the certificates incorrectly :
Client:
step ca certificate "localhost" client.crt client.key
In the below, cert and key variables are client.crt and client.key respectively.
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM([]byte(cert))
// Read the key pair to create certificate
keyPair, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err != nil {
log.Fatal(err)
}
transport = &http.Transport{
IdleConnTimeout: transportIdleConnTimeout,
MaxIdleConnsPerHost: transportMaxIdleConnsPerHost,
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{keyPair},
},
Server:
step ca certificate "pm1" server.crt server.key
In the below, cert and key variables are server.crt and server.key respectively.
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM([]byte(cert))
cert, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err != nil {
log.Fatal("server: loadkeys: ", err)
}
tlsConfig := tls.Config{
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}
I am not using ca.crt (step ca root ca.crt
) at all right now.
答案1
得分: 3
> 0. 当客户端也是服务器时,需要哪些mTLS证书?
从服务器的角度来看:
- 每个服务都需要自己的证书,用于提供HTTPS服务。
- 一个受信任的证书列表,用于验证传入的请求(该列表应包括所有授权客户端使用的证书)。
从客户端的角度来看:
- 需要为所有打算通过mTLS与之通信的服务器提供证书列表;客户端不会信任未知的证书(类似于Web浏览器);通常在这里使用为所有服务签署证书的CA,但如果确实需要,也可以单独添加它们。
- 需要自己的证书,用于签署传出的请求。
> 1. 使用mTLS的客户端将golang http服务器暴露在80端口上。
端口(:80
或:443
等)和协议(http
,https
,gRPC
等)是不同的概念。然而,强烈建议在端口80
上使用http
,在:443
上使用https
。如果你不关心约定,你也可以在端口:80
上使用https
。因为你想实现mTLS,所以你将需要在服务器上监听HTTPS。
> 2. 但它是生成证书的单一来源,并向客户端提供内容,所以为了简单起见,将坚持使用这个命名...是否可能让客户端使用从单一服务器生成的证书来监听请求?
我不确定我是否理解了前提,但证书的生成通常不是服务器或客户端的责任,它们应该使用这些证书来建立相互信任。我假设你有一个可以为每个Web服务器签署证书的CA,并为简单起见将它们命名为:
µA:必须与µB和µC通信
µB:只能与µA通信
µC:只能与µA通信
HTTPS和mTLS又是不同的东西:前者是协议,后者是一种身份验证方法。我将尝试分别解决它们:
HTTPS
:
要开始监听HTTPS
,每个应该接受连接的微服务都应该启动一个监听器,并加载自己的.crt
和.key
(在配置mTLS时我将重新讨论这一部分,但现在让我们解决HTTPS):
func main() {
http.HandleFunc("/hello", HelloServer)
// 如果你真的不关心约定,可以将443替换为80
err := http.ListenAndServeTLS(":443", "µ<A|B|C>.crt", "µ<A|B|C>.key", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
mTLS
:
mTLS
身份验证是在客户端(发送请求的微服务)和服务器(接收请求的微服务)之间相互进行的。每个微服务可以是客户端、服务器或两者兼而有之。
从客户端的角度来看:
- 必须验证并信任服务器提供的证书
- 客户端必须加载自己的证书并对传出的请求进行签名
从服务器的角度来看:
- 传入的请求必须由一组受信任的客户端证书或CA进行签名
- 响应必须使用服务器证书进行签名,该证书应该由发起请求的客户端信任
客户端A的角度:
// 客户端A的角度
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
// 提供为服务器B和C签署的证书的CA
RootCAs: caCertPool,
// 提供微服务A的证书,该证书用于发起请求
Certificates: []tls.Certificate{cert},
},
},
}
服务器B或C的角度:
tlsConfig := &tls.Config{
// 仅在此池中添加微服务A的证书;这将允许A连接到B和C,但不允许B连接到C和C连接到B
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
tlsConfig.BuildNameToCertificate()
server := &http.Server{
Addr: ":443", // 如果你不关心约定,可以使用:80
TLSConfig: tlsConfig,
}
server.ListenAndServeTLS("server.crt", "server.key")
英文:
> 0. What mTLS certificates are required when the client is also the server?
From server perspective:
- each service requires it's own certificate, to serve HTTPS
- a list of trusted certificates, used to authenticate the incoming requests (the list should include all the certificates used by all the authorised clients).
From client perspective:
- the list of certificates for all the servers that you intend to communicate with over mTLS; clients won't trust unknown certificates(similar to a web browser); usually the CA that signed certificates for all services is used here, but you may also add them individually if really want to
- its own certificate which is used to sign the outgoing requests.
> 1. clients using mTLS [...] expose the golang http server on port 80
The port( :80
or :443
etc.) and the protocols(http
, https
, gRPC
etc) are different things. However, there is a strong convention to use http
on port 80
and https
on :443
. If you don't care about conventions, nothing can stop you to use https
on port :80
. Because you want to achieve mTLS, you will have to listen on HTTPS on the servers.
> 2. [...]but it’s a single source for certificate generation and serves content to the clients so will stick with this naming for simplicity[...]and is it possible to have the clients listen for requests using certificates generated from a single server?
I am not sure I understand the premise here, but certificate generation isn't usually in the responsibility of the servers or clients that are supposed to use them to establish mutual trust.
I will assume you have a CA that can sign certificates for each of your web-servers and for simplicity will name them:
µA: must communicate with µB and µC
µB: must communicate only with µA
µC: must communicate only with µA
HTTPS and mTLS are again different things: the first is the protocol and the second is an authentication method. I will try to address them individually:
HTTPS
:
To start listening on HTTPS
, each micro-service which is supposed to accept connections, should start a listener and should load its own .crt
and .key
(I will revisit this section when configuring mTLS, but for the moment let's solve the HTTPS):
func main() {
http.HandleFunc("/hello", HelloServer)
// Replace 443 with 80 if you really don't care about conventions
err := http.ListenAndServeTLS(":443", "µ<A|B|C>.crt", "µ<A|B|C>.key", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
mTLS
:
mTLS
authentication is performed mutually between a client(micro-service sending the request) and a server(micro-service receiving the request). Each micro-service can be a client, a server or both.
From client perspective:
- the certificate presented by the server must be verified and trusted
- the client must load it's own certificate and sign its outgoing requests
From server perspective:
- the incoming requests must be signed by a trusted list of client(s) certificates or CAs
- the response must be signed with a the server certificate, which should be trusted by the client that originated the request.
Client A perspective:
// Client A perspective
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
// provide the CA that signed certificates that are presented
// by servers B and C
RootCAs: caCertPool,
// provide the certificate for microservice A, which is
// initiating the request
Certificates: []tls.Certificate{cert},
},
},
}
Server B or C perspective:
tlsConfig := &tls.Config{
// add only microservice A certificate in this pool; this will allow A
// to connect to B and C, but won't allow B to C and C to B
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
tlsConfig.BuildNameToCertificate()
server := &http.Server{
Addr: ":443", // use :80 if you don't care about conventions
TLSConfig: tlsConfig,
}
server.ListenAndServeTLS("server.crt", "server.key")
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论