github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/credentials/xds/xds.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Package xds provides a transport credentials implementation where the 20 // security configuration is pushed by a management server using xDS APIs. 21 package xds 22 23 import ( 24 "context" 25 "errors" 26 "fmt" 27 "net" 28 "time" 29 30 "github.com/hxx258456/ccgo/x509" 31 32 tls "github.com/hxx258456/ccgo/gmtls" 33 34 "github.com/hxx258456/ccgo/grpc/credentials" 35 credinternal "github.com/hxx258456/ccgo/grpc/internal/credentials" 36 xdsinternal "github.com/hxx258456/ccgo/grpc/internal/credentials/xds" 37 ) 38 39 // ClientOptions contains parameters to configure a new client-side xDS 40 // credentials implementation. 41 type ClientOptions struct { 42 // FallbackCreds specifies the fallback credentials to be used when either 43 // the `xds` scheme is not used in the user's dial target or when the 44 // management server does not return any security configuration. Attempts to 45 // create client credentials without fallback credentials will fail. 46 FallbackCreds credentials.TransportCredentials 47 } 48 49 // NewClientCredentials returns a new client-side transport credentials 50 // implementation which uses xDS APIs to fetch its security configuration. 51 func NewClientCredentials(opts ClientOptions) (credentials.TransportCredentials, error) { 52 if opts.FallbackCreds == nil { 53 return nil, errors.New("missing fallback credentials") 54 } 55 return &credsImpl{ 56 isClient: true, 57 fallback: opts.FallbackCreds, 58 }, nil 59 } 60 61 // ServerOptions contains parameters to configure a new server-side xDS 62 // credentials implementation. 63 type ServerOptions struct { 64 // FallbackCreds specifies the fallback credentials to be used when the 65 // management server does not return any security configuration. Attempts to 66 // create server credentials without fallback credentials will fail. 67 FallbackCreds credentials.TransportCredentials 68 } 69 70 // NewServerCredentials returns a new server-side transport credentials 71 // implementation which uses xDS APIs to fetch its security configuration. 72 func NewServerCredentials(opts ServerOptions) (credentials.TransportCredentials, error) { 73 if opts.FallbackCreds == nil { 74 return nil, errors.New("missing fallback credentials") 75 } 76 return &credsImpl{ 77 isClient: false, 78 fallback: opts.FallbackCreds, 79 }, nil 80 } 81 82 // credsImpl is an implementation of the credentials.TransportCredentials 83 // interface which uses xDS APIs to fetch its security configuration. 84 type credsImpl struct { 85 isClient bool 86 fallback credentials.TransportCredentials 87 } 88 89 // ClientHandshake performs the TLS handshake on the client-side. 90 // 91 // It looks for the presence of a HandshakeInfo value in the passed in context 92 // (added using a call to NewContextWithHandshakeInfo()), and retrieves identity 93 // and root certificates from there. It also retrieves a list of acceptable SANs 94 // and uses a custom verification function to validate the certificate presented 95 // by the peer. It uses fallback credentials if no HandshakeInfo is present in 96 // the passed in context. 97 func (c *credsImpl) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { 98 if !c.isClient { 99 return nil, nil, errors.New("ClientHandshake() is not supported for server credentials") 100 } 101 102 // The CDS balancer constructs a new HandshakeInfo using a call to 103 // NewHandshakeInfo(), and then adds it to the attributes field of the 104 // resolver.Address when handling calls to NewSubConn(). The transport layer 105 // takes care of shipping these attributes in the context to this handshake 106 // function. We first read the credentials.ClientHandshakeInfo type from the 107 // context, which contains the attributes added by the CDS balancer. We then 108 // read the HandshakeInfo from the attributes to get to the actual data that 109 // we need here for the handshake. 110 chi := credentials.ClientHandshakeInfoFromContext(ctx) 111 // If there are no attributes in the received context or the attributes does 112 // not contain a HandshakeInfo, it could either mean that the user did not 113 // specify an `xds` scheme in their dial target or that the xDS server did 114 // not provide any security configuration. In both of these cases, we use 115 // the fallback credentials specified by the user. 116 if chi.Attributes == nil { 117 return c.fallback.ClientHandshake(ctx, authority, rawConn) 118 } 119 hi := xdsinternal.GetHandshakeInfo(chi.Attributes) 120 if hi.UseFallbackCreds() { 121 return c.fallback.ClientHandshake(ctx, authority, rawConn) 122 } 123 124 // We build the tls.Config with the following values 125 // 1. Root certificate as returned by the root provider. 126 // 2. Identity certificate as returned by the identity provider. This may be 127 // empty on the client side, if the client is not doing mTLS. 128 // 3. InsecureSkipVerify to true. Certificates used in Mesh environments 129 // usually contains the identity of the workload presenting the 130 // certificate as a SAN (instead of a hostname in the CommonName field). 131 // This means that normal certificate verification as done by the 132 // standard library will fail. 133 // 4. Key usage to match whether client/server usage. 134 // 5. A `VerifyPeerCertificate` function which performs normal peer 135 // cert verification using configured roots, and the custom SAN checks. 136 cfg, err := hi.ClientSideTLSConfig(ctx) 137 if err != nil { 138 return nil, nil, err 139 } 140 cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 141 // Parse all raw certificates presented by the peer. 142 var certs []*x509.Certificate 143 for _, rc := range rawCerts { 144 cert, err := x509.ParseCertificate(rc) 145 if err != nil { 146 return err 147 } 148 certs = append(certs, cert) 149 } 150 151 // Build the intermediates list and verify that the leaf certificate 152 // is signed by one of the root certificates. 153 intermediates := x509.NewCertPool() 154 for _, cert := range certs[1:] { 155 intermediates.AddCert(cert) 156 } 157 opts := x509.VerifyOptions{ 158 Roots: cfg.RootCAs, 159 Intermediates: intermediates, 160 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 161 } 162 if _, err := certs[0].Verify(opts); err != nil { 163 return err 164 } 165 // The SANs sent by the MeshCA are encoded as SPIFFE IDs. We need to 166 // only look at the SANs on the leaf cert. 167 if !hi.MatchingSANExists(certs[0]) { 168 return fmt.Errorf("SANs received in leaf certificate %+v does not match any of the accepted SANs", certs[0]) 169 } 170 return nil 171 } 172 173 // Perform the TLS handshake with the tls.Config that we have. We run the 174 // actual Handshake() function in a goroutine because we need to respect the 175 // deadline specified on the passed in context, and we need a way to cancel 176 // the handshake if the context is cancelled. 177 conn := tls.Client(rawConn, cfg) 178 errCh := make(chan error, 1) 179 go func() { 180 errCh <- conn.Handshake() 181 close(errCh) 182 }() 183 select { 184 case err := <-errCh: 185 if err != nil { 186 conn.Close() 187 return nil, nil, err 188 } 189 case <-ctx.Done(): 190 conn.Close() 191 return nil, nil, ctx.Err() 192 } 193 info := credentials.TLSInfo{ 194 State: conn.ConnectionState(), 195 CommonAuthInfo: credentials.CommonAuthInfo{ 196 SecurityLevel: credentials.PrivacyAndIntegrity, 197 }, 198 SPIFFEID: credinternal.SPIFFEIDFromState(conn.ConnectionState()), 199 } 200 return credinternal.WrapSyscallConn(rawConn, conn), info, nil 201 } 202 203 // ServerHandshake performs the TLS handshake on the server-side. 204 func (c *credsImpl) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { 205 if c.isClient { 206 return nil, nil, errors.New("ServerHandshake is not supported for client credentials") 207 } 208 209 // An xds-enabled gRPC server wraps the underlying raw net.Conn in a type 210 // that provides a way to retrieve `HandshakeInfo`, which contains the 211 // certificate providers to be used during the handshake. If the net.Conn 212 // passed to this function does not implement this interface, or if the 213 // `HandshakeInfo` does not contain the information we are looking for, we 214 // delegate the handshake to the fallback credentials. 215 hiConn, ok := rawConn.(interface { 216 XDSHandshakeInfo() (*xdsinternal.HandshakeInfo, error) 217 }) 218 if !ok { 219 return c.fallback.ServerHandshake(rawConn) 220 } 221 hi, err := hiConn.XDSHandshakeInfo() 222 if err != nil { 223 return nil, nil, err 224 } 225 if hi.UseFallbackCreds() { 226 return c.fallback.ServerHandshake(rawConn) 227 } 228 229 // An xds-enabled gRPC server is expected to wrap the underlying raw 230 // net.Conn in a type which provides a way to retrieve the deadline set on 231 // it. If we cannot retrieve the deadline here, we fail (by setting deadline 232 // to time.Now()), instead of using a default deadline and possibly taking 233 // longer to eventually fail. 234 deadline := time.Now() 235 if dConn, ok := rawConn.(interface{ GetDeadline() time.Time }); ok { 236 deadline = dConn.GetDeadline() 237 } 238 ctx, cancel := context.WithDeadline(context.Background(), deadline) 239 defer cancel() 240 cfg, err := hi.ServerSideTLSConfig(ctx) 241 if err != nil { 242 return nil, nil, err 243 } 244 245 conn := tls.Server(rawConn, cfg) 246 if err := conn.Handshake(); err != nil { 247 conn.Close() 248 return nil, nil, err 249 } 250 info := credentials.TLSInfo{ 251 State: conn.ConnectionState(), 252 CommonAuthInfo: credentials.CommonAuthInfo{ 253 SecurityLevel: credentials.PrivacyAndIntegrity, 254 }, 255 } 256 info.SPIFFEID = credinternal.SPIFFEIDFromState(conn.ConnectionState()) 257 return credinternal.WrapSyscallConn(rawConn, conn), info, nil 258 } 259 260 // Info provides the ProtocolInfo of this TransportCredentials. 261 func (c *credsImpl) Info() credentials.ProtocolInfo { 262 return credentials.ProtocolInfo{SecurityProtocol: "tls"} 263 } 264 265 // Clone makes a copy of this TransportCredentials. 266 func (c *credsImpl) Clone() credentials.TransportCredentials { 267 clone := *c 268 return &clone 269 } 270 271 func (c *credsImpl) OverrideServerName(_ string) error { 272 return errors.New("serverName for peer validation must be configured as a list of acceptable SANs") 273 } 274 275 // UsesXDS returns true if c uses xDS to fetch security configuration 276 // used at handshake time, and false otherwise. 277 func (c *credsImpl) UsesXDS() bool { 278 return true 279 }