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