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