| 174 | } |
| 175 | |
| 176 | func loadTLSCreds(cfg Config) (credentials.TransportCredentials, error) { |
| 177 | if cfg.CertFile == "" || cfg.KeyFile == "" || cfg.CAFile == "" { |
| 178 | return nil, errors.New("CertFile/KeyFile/CAFile required when Dialer is not set") |
| 179 | } |
| 180 | clientCert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) |
| 181 | if err != nil { |
| 182 | return nil, fmt.Errorf("load client keypair: %w", err) |
| 183 | } |
| 184 | caPEM, err := os.ReadFile(cfg.CAFile) |
| 185 | if err != nil { |
| 186 | return nil, fmt.Errorf("read CA: %w", err) |
| 187 | } |
| 188 | pool := x509.NewCertPool() |
| 189 | if !pool.AppendCertsFromPEM(caPEM) { |
| 190 | return nil, errors.New("invalid CA PEM") |
| 191 | } |
| 192 | // 只校验 server 证书是否由受信任的 NodeCA 签发(CA pinning), |
| 193 | // 不校验 hostname/IP SAN,使控制面 IP 变动后无需重签证书。 |
| 194 | return credentials.NewTLS(&tls.Config{ |
| 195 | InsecureSkipVerify: true, //nolint:gosec // 见下方 VerifyPeerCertificate |
| 196 | Certificates: []tls.Certificate{clientCert}, |
| 197 | MinVersion: tls.VersionTLS12, |
| 198 | VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error { |
| 199 | certs := make([]*x509.Certificate, 0, len(rawCerts)) |
| 200 | for _, raw := range rawCerts { |
| 201 | c, err := x509.ParseCertificate(raw) |
| 202 | if err != nil { |
| 203 | return err |
| 204 | } |
| 205 | certs = append(certs, c) |
| 206 | } |
| 207 | if len(certs) == 0 { |
| 208 | return errors.New("server sent no certificates") |
| 209 | } |
| 210 | intermediates := x509.NewCertPool() |
| 211 | for _, c := range certs[1:] { |
| 212 | intermediates.AddCert(c) |
| 213 | } |
| 214 | _, err := certs[0].Verify(x509.VerifyOptions{ |
| 215 | Roots: pool, |
| 216 | Intermediates: intermediates, |
| 217 | }) |
| 218 | return err |
| 219 | }, |
| 220 | }), nil |
| 221 | } |
| 222 | |
| 223 | // keepaliveParams 返回 grpc.WithKeepaliveParams 选项。 |
| 224 | // PermitWithoutStream=true 让 client 在没有 active stream 时也能维持 ping, |