| /* |
| * |
| * Copyright 2020 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| // Package xds provides a transport credentials implementation where the |
| // security configuration is pushed by a management server using xDS APIs. |
| // |
| // Experimental |
| // |
| // Notice: All APIs in this package are EXPERIMENTAL and may be removed in a |
| // later release. |
| package xds |
| |
| import ( |
| "context" |
| "crypto/tls" |
| "crypto/x509" |
| "errors" |
| "fmt" |
| "net" |
| "time" |
| |
| "google.golang.org/grpc/credentials" |
| credinternal "google.golang.org/grpc/internal/credentials" |
| xdsinternal "google.golang.org/grpc/internal/credentials/xds" |
| ) |
| |
| // ClientOptions contains parameters to configure a new client-side xDS |
| // credentials implementation. |
| type ClientOptions struct { |
| // FallbackCreds specifies the fallback credentials to be used when either |
| // the `xds` scheme is not used in the user's dial target or when the |
| // management server does not return any security configuration. Attempts to |
| // create client credentials without fallback credentials will fail. |
| FallbackCreds credentials.TransportCredentials |
| } |
| |
| // NewClientCredentials returns a new client-side transport credentials |
| // implementation which uses xDS APIs to fetch its security configuration. |
| func NewClientCredentials(opts ClientOptions) (credentials.TransportCredentials, error) { |
| if opts.FallbackCreds == nil { |
| return nil, errors.New("missing fallback credentials") |
| } |
| return &credsImpl{ |
| isClient: true, |
| fallback: opts.FallbackCreds, |
| }, nil |
| } |
| |
| // ServerOptions contains parameters to configure a new server-side xDS |
| // credentials implementation. |
| type ServerOptions struct { |
| // FallbackCreds specifies the fallback credentials to be used when the |
| // management server does not return any security configuration. Attempts to |
| // create server credentials without fallback credentials will fail. |
| FallbackCreds credentials.TransportCredentials |
| } |
| |
| // NewServerCredentials returns a new server-side transport credentials |
| // implementation which uses xDS APIs to fetch its security configuration. |
| func NewServerCredentials(opts ServerOptions) (credentials.TransportCredentials, error) { |
| if opts.FallbackCreds == nil { |
| return nil, errors.New("missing fallback credentials") |
| } |
| return &credsImpl{ |
| isClient: false, |
| fallback: opts.FallbackCreds, |
| }, nil |
| } |
| |
| // credsImpl is an implementation of the credentials.TransportCredentials |
| // interface which uses xDS APIs to fetch its security configuration. |
| type credsImpl struct { |
| isClient bool |
| fallback credentials.TransportCredentials |
| } |
| |
| // ClientHandshake performs the TLS handshake on the client-side. |
| // |
| // It looks for the presence of a HandshakeInfo value in the passed in context |
| // (added using a call to NewContextWithHandshakeInfo()), and retrieves identity |
| // and root certificates from there. It also retrieves a list of acceptable SANs |
| // and uses a custom verification function to validate the certificate presented |
| // by the peer. It uses fallback credentials if no HandshakeInfo is present in |
| // the passed in context. |
| func (c *credsImpl) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { |
| if !c.isClient { |
| return nil, nil, errors.New("ClientHandshake() is not supported for server credentials") |
| } |
| |
| // The CDS balancer constructs a new HandshakeInfo using a call to |
| // NewHandshakeInfo(), and then adds it to the attributes field of the |
| // resolver.Address when handling calls to NewSubConn(). The transport layer |
| // takes care of shipping these attributes in the context to this handshake |
| // function. We first read the credentials.ClientHandshakeInfo type from the |
| // context, which contains the attributes added by the CDS balancer. We then |
| // read the HandshakeInfo from the attributes to get to the actual data that |
| // we need here for the handshake. |
| chi := credentials.ClientHandshakeInfoFromContext(ctx) |
| // If there are no attributes in the received context or the attributes does |
| // not contain a HandshakeInfo, it could either mean that the user did not |
| // specify an `xds` scheme in their dial target or that the xDS server did |
| // not provide any security configuration. In both of these cases, we use |
| // the fallback credentials specified by the user. |
| if chi.Attributes == nil { |
| return c.fallback.ClientHandshake(ctx, authority, rawConn) |
| } |
| hi := xdsinternal.GetHandshakeInfo(chi.Attributes) |
| if hi.UseFallbackCreds() { |
| return c.fallback.ClientHandshake(ctx, authority, rawConn) |
| } |
| |
| // We build the tls.Config with the following values |
| // 1. Root certificate as returned by the root provider. |
| // 2. Identity certificate as returned by the identity provider. This may be |
| // empty on the client side, if the client is not doing mTLS. |
| // 3. InsecureSkipVerify to true. Certificates used in Mesh environments |
| // usually contains the identity of the workload presenting the |
| // certificate as a SAN (instead of a hostname in the CommonName field). |
| // This means that normal certificate verification as done by the |
| // standard library will fail. |
| // 4. Key usage to match whether client/server usage. |
| // 5. A `VerifyPeerCertificate` function which performs normal peer |
| // cert verification using configured roots, and the custom SAN checks. |
| cfg, err := hi.ClientSideTLSConfig(ctx) |
| if err != nil { |
| return nil, nil, err |
| } |
| cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { |
| // Parse all raw certificates presented by the peer. |
| var certs []*x509.Certificate |
| for _, rc := range rawCerts { |
| cert, err := x509.ParseCertificate(rc) |
| if err != nil { |
| return err |
| } |
| certs = append(certs, cert) |
| } |
| |
| // Build the intermediates list and verify that the leaf certificate |
| // is signed by one of the root certificates. |
| intermediates := x509.NewCertPool() |
| for _, cert := range certs[1:] { |
| intermediates.AddCert(cert) |
| } |
| opts := x509.VerifyOptions{ |
| Roots: cfg.RootCAs, |
| Intermediates: intermediates, |
| KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, |
| } |
| if _, err := certs[0].Verify(opts); err != nil { |
| return err |
| } |
| // The SANs sent by the MeshCA are encoded as SPIFFE IDs. We need to |
| // only look at the SANs on the leaf cert. |
| if !hi.MatchingSANExists(certs[0]) { |
| return fmt.Errorf("SANs received in leaf certificate %+v does not match any of the accepted SANs", certs[0]) |
| } |
| return nil |
| } |
| |
| // Perform the TLS handshake with the tls.Config that we have. We run the |
| // actual Handshake() function in a goroutine because we need to respect the |
| // deadline specified on the passed in context, and we need a way to cancel |
| // the handshake if the context is cancelled. |
| conn := tls.Client(rawConn, cfg) |
| errCh := make(chan error, 1) |
| go func() { |
| errCh <- conn.Handshake() |
| close(errCh) |
| }() |
| select { |
| case err := <-errCh: |
| if err != nil { |
| conn.Close() |
| return nil, nil, err |
| } |
| case <-ctx.Done(): |
| conn.Close() |
| return nil, nil, ctx.Err() |
| } |
| info := credentials.TLSInfo{ |
| State: conn.ConnectionState(), |
| CommonAuthInfo: credentials.CommonAuthInfo{ |
| SecurityLevel: credentials.PrivacyAndIntegrity, |
| }, |
| SPIFFEID: credinternal.SPIFFEIDFromState(conn.ConnectionState()), |
| } |
| return credinternal.WrapSyscallConn(rawConn, conn), info, nil |
| } |
| |
| // ServerHandshake performs the TLS handshake on the server-side. |
| func (c *credsImpl) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { |
| if c.isClient { |
| return nil, nil, errors.New("ServerHandshake is not supported for client credentials") |
| } |
| |
| // An xds-enabled gRPC server wraps the underlying raw net.Conn in a type |
| // that provides a way to retrieve `HandshakeInfo`, which contains the |
| // certificate providers to be used during the handshake. If the net.Conn |
| // passed to this function does not implement this interface, or if the |
| // `HandshakeInfo` does not contain the information we are looking for, we |
| // delegate the handshake to the fallback credentials. |
| hiConn, ok := rawConn.(interface { |
| XDSHandshakeInfo() (*xdsinternal.HandshakeInfo, error) |
| }) |
| if !ok { |
| return c.fallback.ServerHandshake(rawConn) |
| } |
| hi, err := hiConn.XDSHandshakeInfo() |
| if err != nil { |
| return nil, nil, err |
| } |
| if hi.UseFallbackCreds() { |
| return c.fallback.ServerHandshake(rawConn) |
| } |
| |
| // An xds-enabled gRPC server is expected to wrap the underlying raw |
| // net.Conn in a type which provides a way to retrieve the deadline set on |
| // it. If we cannot retrieve the deadline here, we fail (by setting deadline |
| // to time.Now()), instead of using a default deadline and possibly taking |
| // longer to eventually fail. |
| deadline := time.Now() |
| if dConn, ok := rawConn.(interface{ GetDeadline() time.Time }); ok { |
| deadline = dConn.GetDeadline() |
| } |
| ctx, cancel := context.WithDeadline(context.Background(), deadline) |
| defer cancel() |
| cfg, err := hi.ServerSideTLSConfig(ctx) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| conn := tls.Server(rawConn, cfg) |
| if err := conn.Handshake(); err != nil { |
| conn.Close() |
| return nil, nil, err |
| } |
| info := credentials.TLSInfo{ |
| State: conn.ConnectionState(), |
| CommonAuthInfo: credentials.CommonAuthInfo{ |
| SecurityLevel: credentials.PrivacyAndIntegrity, |
| }, |
| } |
| info.SPIFFEID = credinternal.SPIFFEIDFromState(conn.ConnectionState()) |
| return credinternal.WrapSyscallConn(rawConn, conn), info, nil |
| } |
| |
| // Info provides the ProtocolInfo of this TransportCredentials. |
| func (c *credsImpl) Info() credentials.ProtocolInfo { |
| return credentials.ProtocolInfo{SecurityProtocol: "tls"} |
| } |
| |
| // Clone makes a copy of this TransportCredentials. |
| func (c *credsImpl) Clone() credentials.TransportCredentials { |
| clone := *c |
| return &clone |
| } |
| |
| func (c *credsImpl) OverrideServerName(_ string) error { |
| return errors.New("serverName for peer validation must be configured as a list of acceptable SANs") |
| } |
| |
| // UsesXDS returns true if c uses xDS to fetch security configuration |
| // used at handshake time, and false otherwise. |
| func (c *credsImpl) UsesXDS() bool { |
| return true |
| } |