1 /*
2  * Copyright 2018 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.alts;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 
21 import com.google.common.annotations.VisibleForTesting;
22 import io.grpc.CallOptions;
23 import io.grpc.Channel;
24 import io.grpc.ClientCall;
25 import io.grpc.ClientInterceptor;
26 import io.grpc.ExperimentalApi;
27 import io.grpc.ForwardingChannelBuilder;
28 import io.grpc.ManagedChannel;
29 import io.grpc.ManagedChannelBuilder;
30 import io.grpc.MethodDescriptor;
31 import io.grpc.Status;
32 import io.grpc.alts.internal.AltsClientOptions;
33 import io.grpc.alts.internal.AltsProtocolNegotiator;
34 import io.grpc.alts.internal.AltsTsiHandshaker;
35 import io.grpc.alts.internal.HandshakerServiceGrpc;
36 import io.grpc.alts.internal.RpcProtocolVersionsUtil;
37 import io.grpc.alts.internal.TsiHandshaker;
38 import io.grpc.alts.internal.TsiHandshakerFactory;
39 import io.grpc.internal.GrpcUtil;
40 import io.grpc.internal.ObjectPool;
41 import io.grpc.internal.ProxyParameters;
42 import io.grpc.internal.SharedResourcePool;
43 import io.grpc.netty.InternalNettyChannelBuilder;
44 import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilter;
45 import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilterFactory;
46 import io.grpc.netty.NettyChannelBuilder;
47 import java.net.InetSocketAddress;
48 import java.net.SocketAddress;
49 import java.util.concurrent.TimeUnit;
50 import java.util.logging.Level;
51 import java.util.logging.Logger;
52 import javax.annotation.Nullable;
53 
54 /**
55  * ALTS version of {@code ManagedChannelBuilder}. This class sets up a secure and authenticated
56  * commmunication between two cloud VMs using ALTS.
57  */
58 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4151")
59 public final class AltsChannelBuilder extends ForwardingChannelBuilder<AltsChannelBuilder> {
60 
61   private static final Logger logger = Logger.getLogger(AltsChannelBuilder.class.getName());
62   private final NettyChannelBuilder delegate;
63   private final AltsClientOptions.Builder handshakerOptionsBuilder =
64       new AltsClientOptions.Builder();
65   private ObjectPool<ManagedChannel> handshakerChannelPool =
66       SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL);
67   private TcpfFactory tcpfFactoryForTest;
68   private boolean enableUntrustedAlts;
69 
70   /** "Overrides" the static method in {@link ManagedChannelBuilder}. */
forTarget(String target)71   public static final AltsChannelBuilder forTarget(String target) {
72     return new AltsChannelBuilder(target);
73   }
74 
75   /** "Overrides" the static method in {@link ManagedChannelBuilder}. */
forAddress(String name, int port)76   public static AltsChannelBuilder forAddress(String name, int port) {
77     return forTarget(GrpcUtil.authorityFromHostAndPort(name, port));
78   }
79 
AltsChannelBuilder(String target)80   private AltsChannelBuilder(String target) {
81     delegate =
82         NettyChannelBuilder.forTarget(target)
83             .keepAliveTime(20, TimeUnit.SECONDS)
84             .keepAliveTimeout(10, TimeUnit.SECONDS)
85             .keepAliveWithoutCalls(true);
86     handshakerOptionsBuilder.setRpcProtocolVersions(
87         RpcProtocolVersionsUtil.getRpcProtocolVersions());
88   }
89 
90   /** The server service account name for secure name checking. */
withSecureNamingTarget(String targetName)91   public AltsChannelBuilder withSecureNamingTarget(String targetName) {
92     handshakerOptionsBuilder.setTargetName(targetName);
93     return this;
94   }
95 
96   /**
97    * Adds an expected target service accounts. One of the added service accounts should match peer
98    * service account in the handshaker result. Otherwise, the handshake fails.
99    */
addTargetServiceAccount(String targetServiceAccount)100   public AltsChannelBuilder addTargetServiceAccount(String targetServiceAccount) {
101     handshakerOptionsBuilder.addTargetServiceAccount(targetServiceAccount);
102     return this;
103   }
104 
105   /**
106    * Enables untrusted ALTS for testing. If this function is called, we will not check whether ALTS
107    * is running on Google Cloud Platform.
108    */
enableUntrustedAltsForTesting()109   public AltsChannelBuilder enableUntrustedAltsForTesting() {
110     enableUntrustedAlts = true;
111     return this;
112   }
113 
114   /** Sets a new handshaker service address for testing. */
setHandshakerAddressForTesting(String handshakerAddress)115   public AltsChannelBuilder setHandshakerAddressForTesting(String handshakerAddress) {
116     // Instead of using the default shared channel to the handshaker service, create a fix object
117     // pool of handshaker service channel for testing.
118     handshakerChannelPool =
119         HandshakerServiceChannel.getHandshakerChannelPoolForTesting(handshakerAddress);
120     return this;
121   }
122 
123   @Override
delegate()124   protected NettyChannelBuilder delegate() {
125     return delegate;
126   }
127 
128   @Override
build()129   public ManagedChannel build() {
130     if (!CheckGcpEnvironment.isOnGcp()) {
131       if (enableUntrustedAlts) {
132         logger.log(
133             Level.WARNING,
134             "Untrusted ALTS mode is enabled and we cannot guarantee the trustworthiness of the "
135                 + "ALTS handshaker service");
136       } else {
137         Status status =
138             Status.INTERNAL.withDescription("ALTS is only allowed to run on Google Cloud Platform");
139         delegate().intercept(new FailingClientInterceptor(status));
140       }
141     }
142 
143     final AltsClientOptions handshakerOptions = handshakerOptionsBuilder.build();
144     TsiHandshakerFactory altsHandshakerFactory =
145         new TsiHandshakerFactory() {
146           @Override
147           public TsiHandshaker newHandshaker() {
148             // Used the shared grpc channel to connecting to the ALTS handshaker service.
149             // TODO: Release the channel if it is not used.
150             // https://github.com/grpc/grpc-java/issues/4755.
151             return AltsTsiHandshaker.newClient(
152                 HandshakerServiceGrpc.newStub(handshakerChannelPool.getObject()),
153                 handshakerOptions);
154           }
155         };
156     AltsProtocolNegotiator negotiator = AltsProtocolNegotiator.create(altsHandshakerFactory);
157 
158     TcpfFactory tcpfFactory = new TcpfFactory(handshakerOptions, negotiator);
159     InternalNettyChannelBuilder.setDynamicTransportParamsFactory(delegate(), tcpfFactory);
160     tcpfFactoryForTest = tcpfFactory;
161 
162     return delegate().build();
163   }
164 
165   @VisibleForTesting
166   @Nullable
getTcpfFactoryForTest()167   TransportCreationParamsFilterFactory getTcpfFactoryForTest() {
168     return tcpfFactoryForTest;
169   }
170 
171   @VisibleForTesting
172   @Nullable
getAltsClientOptionsForTest()173   AltsClientOptions getAltsClientOptionsForTest() {
174     if (tcpfFactoryForTest == null) {
175       return null;
176     }
177     return tcpfFactoryForTest.handshakerOptions;
178   }
179 
180   private static final class TcpfFactory implements TransportCreationParamsFilterFactory {
181 
182     final AltsClientOptions handshakerOptions;
183     private final AltsProtocolNegotiator negotiator;
184 
TcpfFactory(AltsClientOptions handshakerOptions, AltsProtocolNegotiator negotiator)185     public TcpfFactory(AltsClientOptions handshakerOptions, AltsProtocolNegotiator negotiator) {
186       this.handshakerOptions = handshakerOptions;
187       this.negotiator = negotiator;
188     }
189 
190     @Override
create( final SocketAddress serverAddress, final String authority, final String userAgent, final ProxyParameters proxy)191     public TransportCreationParamsFilter create(
192         final SocketAddress serverAddress,
193         final String authority,
194         final String userAgent,
195         final ProxyParameters proxy) {
196       checkArgument(
197           serverAddress instanceof InetSocketAddress,
198           "%s must be a InetSocketAddress",
199           serverAddress);
200       return new TransportCreationParamsFilter() {
201         @Override
202         public SocketAddress getTargetServerAddress() {
203           return serverAddress;
204         }
205 
206         @Override
207         public String getAuthority() {
208           return authority;
209         }
210 
211         @Override
212         public String getUserAgent() {
213           return userAgent;
214         }
215 
216         @Override
217         public AltsProtocolNegotiator getProtocolNegotiator() {
218           return negotiator;
219         }
220       };
221     }
222   }
223 
224   /** An implementation of {@link ClientInterceptor} that fails each call. */
225   static final class FailingClientInterceptor implements ClientInterceptor {
226 
227     private final Status status;
228 
229     public FailingClientInterceptor(Status status) {
230       this.status = status;
231     }
232 
233     @Override
234     public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
235         MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
236       return new FailingClientCall<>(status);
237     }
238   }
239 }
240