Skip to content

Commit 24b48fc

Browse files
committed
feat: add TLS support for gRPC connections to Dex
1 parent 9ca08f3 commit 24b48fc

File tree

4 files changed

+88
-14
lines changed

4 files changed

+88
-14
lines changed

config/config.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77
"net/url"
88
"os"
99

10+
"crypto/tls"
11+
"crypto/x509"
12+
1013
"gopkg.in/yaml.v3"
1114
)
1215

@@ -45,7 +48,10 @@ type DynamicClientRegistration struct {
4548
}
4649

4750
type DexGRPCClient struct {
48-
Addr string `yaml:"addr"`
51+
Addr string `yaml:"addr"`
52+
TLSCert string `yaml:"tlsCert,omitempty"`
53+
TLSKey string `yaml:"tlsKey,omitempty"`
54+
TLSClientCA string `yaml:"tlsClientCA,omitempty"`
4955
}
5056

5157
type Proxy struct {
@@ -133,6 +139,50 @@ func (c *Config) YAMLString() (string, error) {
133139
}
134140
}
135141

142+
func (g *DexGRPCClient) ClientTLSConfig() (*tls.Config, error) {
143+
// Check if TLS fields are set - must be all or nothing
144+
tlsFieldsSet := 0
145+
if g.TLSCert != "" {
146+
tlsFieldsSet++
147+
}
148+
if g.TLSKey != "" {
149+
tlsFieldsSet++
150+
}
151+
if g.TLSClientCA != "" {
152+
tlsFieldsSet++
153+
}
154+
155+
if tlsFieldsSet == 0 {
156+
// No TLS configured - return nil for insecure connection
157+
return nil, nil
158+
}
159+
160+
if tlsFieldsSet != 3 {
161+
return nil, fmt.Errorf("all three TLS fields (tlsCert, tlsKey, tlsClientCA) must be set together or all left empty")
162+
}
163+
164+
// All three fields are set - configure mTLS
165+
cPool := x509.NewCertPool()
166+
caCert, err := os.ReadFile(g.TLSClientCA)
167+
if err != nil {
168+
return nil, fmt.Errorf("failed to read TLS client CA: %w", err)
169+
}
170+
if !cPool.AppendCertsFromPEM(caCert) {
171+
return nil, fmt.Errorf("failed to append TLS client CA certificate")
172+
}
173+
174+
clientCert, err := tls.LoadX509KeyPair(g.TLSCert, g.TLSKey)
175+
if err != nil {
176+
return nil, fmt.Errorf("failed to load TLS certificate and key: %w", err)
177+
}
178+
179+
clientTLSConfig := &tls.Config{
180+
RootCAs: cPool,
181+
Certificates: []tls.Certificate{clientCert},
182+
}
183+
return clientTLSConfig, nil
184+
}
185+
136186
func (c *Config) Validate() error {
137187
if c.Host == nil {
138188
return fmt.Errorf("host is required")
@@ -150,6 +200,12 @@ func (c *Config) Validate() error {
150200
if c.DexGRPCClient == nil || c.DexGRPCClient.Addr == "" {
151201
return fmt.Errorf("dexGRPCClient is required when dynamicClientRegistrationEnabled is true")
152202
}
203+
204+
_, err := c.DexGRPCClient.ClientTLSConfig()
205+
if err != nil {
206+
return fmt.Errorf("dexGRPCClient TLS configuration is invalid: %w", err)
207+
}
208+
153209
}
154210

155211
return nil

examples/who-am-i/config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ authorization:
88
publicClient: true
99
dexGRPCClient:
1010
addr: dex:5557
11+
tlsCert: /certs/client.crt
12+
tlsKey: /certs/client.key
13+
tlsClientCA: /certs/ca.crt
1114
proxy:
1215
- path: /who-am-i/mcp
1316
http:

examples/who-am-i/docker-compose.yaml

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: who-am-i-gateway
1+
name: demo-tls-grpc
22

33
services:
44
dex:
@@ -14,6 +14,11 @@ services:
1414
configs:
1515
- source: dex-config.yaml
1616
target: /config.yaml
17+
volumes:
18+
- type: bind
19+
source: ./certs
20+
target: /certs
21+
read_only: true
1722
env_file:
1823
- .dex.secret.env
1924

@@ -47,28 +52,24 @@ services:
4752
configs:
4853
dex-config.yaml:
4954
content: |
50-
issuer: http://localhost:5556
55+
issuer: http://10.239.117.77:5556
5156
web:
5257
http: 0.0.0.0:5556
5358
allowedOrigins: ['*']
5459
grpc:
5560
addr: 0.0.0.0:5557
61+
tlsCert: /server.crt
62+
tlsKey: /server.key
63+
tlsClientCA: /ca.crt
5664
storage:
5765
type: memory
5866
oauth2:
5967
skipApprovalScreen: true
60-
enablePasswordDB: true
61-
staticPasswords:
62-
- email: "admin@example.com"
63-
# bcrypt hash of the string "password" for user admin: $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
64-
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
65-
username: "admin"
66-
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
6768
connectors:
6869
- type: github
6970
id: github
7071
name: GitHub
7172
config:
72-
clientID: {{ .Env.GITHUB_CLIENT_ID }}
73-
clientSecret: {{ .Env.GITHUB_CLIENT_SECRET }}
74-
redirectURI: http://localhost:5556/callback
73+
clientID: 898c3748f5d34f9d8aa6
74+
clientSecret: f79cc67c564c6ac912e108cbefe289d8a5c042f4
75+
redirectURI: http://10.239.117.77:5556/callback

oauth/dynamic_client_registration.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/hyprmcp/mcp-gateway/config"
1212
"github.com/hyprmcp/mcp-gateway/log"
1313
"google.golang.org/grpc"
14+
"google.golang.org/grpc/credentials"
1415
"google.golang.org/grpc/credentials/insecure"
1516
)
1617

@@ -27,9 +28,22 @@ type ClientInformation struct {
2728
}
2829

2930
func NewDynamicClientRegistrationHandler(config *config.Config, meta map[string]any) (http.Handler, error) {
31+
clientTLSConfig, err := config.DexGRPCClient.ClientTLSConfig()
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
var creds credentials.TransportCredentials
37+
38+
if clientTLSConfig != nil {
39+
creds = credentials.NewTLS(clientTLSConfig)
40+
} else {
41+
creds = insecure.NewCredentials()
42+
}
43+
3044
grpcClient, err := grpc.NewClient(
3145
config.DexGRPCClient.Addr,
32-
grpc.WithTransportCredentials(insecure.NewCredentials()),
46+
grpc.WithTransportCredentials(creds),
3347
)
3448
if err != nil {
3549
return nil, err

0 commit comments

Comments
 (0)