8000 feat: Add experimental S2A integration in client libraries grpc trans… · rmehta19/sdk-platform-java@1138ca6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1138ca6

Browse files
rmehta19blakeli0
andauthored
feat: Add experimental S2A integration in client libraries grpc transport (googleapis#3326)
Modify the Client Libraries gRPC Channel builder to use mTLS via S2A if the experimental environment variable is set, S2A is available (We check this by using utility added in googleapis/google-auth-library-java#1400), and a few more conditions (see `shouldUseS2A`). Following https://google.aip.dev/auth/4115, Only attempt to use S2A after DirectPath and DCA (https://google.aip.dev/auth/4114) are ruled out as options. If conditions to use S2A are not met (env variable not set, or S2A is not running in environment, etc (`shouldUseS2A` returns false)), fall back to default TLS connection. When we are creating S2A-enabled Grpc Channel Credentials, we first try to secure the connection between the client and the S2A via MTLS, using [MTLS-MDS](https://cloud.google.com/compute/docs/metadata/overview#https-mds) credentials. If MTLS-MDS credentials can't be loaded, then we fallback to a plaintext connection between the client and S2A. The parallel go implementation : googleapis/google-api-go-client#1874 (now lives here: https://github.com/googleapis/google-cloud-go/blob/main/auth/internal/transport/cba.go) S2A Java client: https://github.com/grpc/grpc-java/tree/master/s2a Resolving b/376258193 means that S2A.java is no longer experimental --------- Co-authored-by: blakeli <blakeli@google.com>
1 parent b20624c commit 1138ca6
  • gax-httpjson/src/main/java/com/google/api/gax/httpjson
  • 20 files changed

    +570
    -4
    lines changed

    WORKSPACE

    Lines changed: 0 additions & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -65,7 +65,6 @@ maven_install(
    6565
    "com.google.api:gapic-generator-java:" + _gapic_generator_java_version,
    6666
    ] + PROTOBUF_MAVEN_ARTIFACTS + IO_GRPC_GRPC_JAVA_ARTIFACTS,
    6767
    fail_on_missing_checksum = False,
    68-
    override_targets = IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS,
    6968
    repositories = [
    7069
    "m2Local",
    7170
    "https://repo.maven.apache.org/maven2/",

    gax-java/dependencies.properties

    Lines changed: 1 addition & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -37,7 +37,7 @@ version.io_grpc=1.68.1
    3737
    # 2) Replace all characters which are neither alphabetic nor digits with the underscore ('_') character
    3838
    maven.com_google_api_grpc_proto_google_common_protos=com.google.api.grpc:proto-google-common-protos:2.46.0
    3939
    maven.com_google_api_grpc_grpc_google_common_protos=com.google.api.grpc:grpc-google-common-protos:2.46.0
    40-
    maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.29.0
    40+
    maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.30.0
    4141
    maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.30.0
    4242
    maven.io_opentelemetry_opentelemetry_api=io.opentelemetry:opentelemetry-api:1.42.1
    4343
    maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.31.1

    gax-java/gax-grpc/BUILD.bazel

    Lines changed: 1 addition & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -28,6 +28,7 @@ _COMPILE_DEPS = [
    2828
    "@io_grpc_grpc_netty_shaded//jar",
    2929
    "@io_grpc_grpc_grpclb//jar",
    3030
    "@io_grpc_grpc_java//alts:alts",
    31+
    "@io_grpc_grpc_java//s2a:s2av2_credentials",
    3132
    "@io_netty_netty_tcnative_boringssl_static//jar",
    3233
    "@javax_annotation_javax_annotation_api//jar",
    3334
    "//gax:gax",

    gax-java/gax-grpc/pom.xml

    Lines changed: 4 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -63,6 +63,10 @@
    6363
    <groupId>io.grpc</groupId>
    6464
    <artifactId>grpc-protobuf</artifactId>
    6565
    </dependency>
    66+
    <dependency>
    67+
    <groupId>io.grpc</groupId>
    68+
    <artifactId>grpc-s2a</artifactId>
    69+
    </dependency>
    6670
    <dependency>
    6771
    <groupId>io.grpc</groupId>
    6872
    <artifactId>grpc-stub</artifactId>

    gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java

    Lines changed: 155 additions & 1 deletion
    < F987 /tr>
    Original file line numberDiff line numberDiff line change
    @@ -46,19 +46,24 @@
    4646
    import com.google.auth.ApiKeyCredentials;
    4747
    import com.google.auth.Credentials;
    4848
    import com.google.auth.oauth2.ComputeEngineCredentials;
    49+
    import com.google.auth.oauth2.SecureSessionAgent;
    50+
    import com.google.auth.oauth2.SecureSessionAgentConfig;
    4951
    import com.google.common.annotations.VisibleForTesting;
    5052
    import com.google.common.base.Preconditions;
    53+
    import com.google.common.base.Strings;
    5154
    import com.google.common.collect.ImmutableList;
    5255
    import com.google.common.collect.ImmutableMap;
    5356
    import com.google.common.io.Files;
    5457
    import io.grpc.CallCredentials;
    5558
    import io.grpc.ChannelCredentials;
    5659
    import io.grpc.Grpc;
    60+
    import io.grpc.InsecureChannelCredentials;
    5761
    import io.grpc.ManagedChannel;
    5862
    import io.grpc.ManagedChannelBuilder;
    5963
    import io.grpc.TlsChannelCredentials;
    6064
    import io.grpc.alts.GoogleDefaultChannelCredentials;
    6165
    import io.grpc.auth.MoreCallCredentials;
    66+
    import io.grpc.s2a.S2AChannelCredentials;
    6267
    import java.io.File;
    6368
    import java.io.IOException;
    6469
    import java.nio.charset.StandardCharsets;
    @@ -99,6 +104,15 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
    99104
    @VisibleForTesting
    100105
    static final String DIRECT_PATH_ENV_ENABLE_XDS = "GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS";
    101106

    107+
    // The public portion of the mTLS MDS root certificate is stored for performing
    108+
    // cert verification when establishing an mTLS connection with the MDS. See
    109+
    // https://cloud.google.com/compute/docs/metadata/overview#https-mds-root-certs
    110+
    private static final String MTLS_MDS_ROOT_PATH = "/run/google-mds-mtls/root.crt";
    111+
    // The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain
    112+
    // followed by a PEM-encoded private key. See
    113+
    // https://cloud.google.com/compute/docs/metadata/overview#https-mds-client-certs
    114+
    private static final String MTLS_MDS_CERT_CHAIN_AND_KEY_PATH = "/run/google-mds-mtls/client.key";
    115+
    102116
    static final long DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS = 3600;
    103117
    static final long DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS = 20;
    104118
    static final String GCE_PRODUCTION_NAME_PRIOR_2016 = "Google";
    @@ -107,6 +121,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
    107121
    private final int processorCount;
    108122
    private final Executor executor;
    109123
    private final HeaderProvider headerProvider;
    124+
    private final boolean useS2A;
    110125
    private final String endpoint;
    111126
    // TODO: remove. envProvider currently provides DirectPath environment variable, and is only used
    112127
    // during initial rollout for DirectPath. This provider will be removed once the DirectPath
    @@ -126,6 +141,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
    126141
    @Nullable private final Boolean allowNonDefaultServiceAccount;
    127142
    @VisibleForTesting final ImmutableMap<String, ?> directPathServiceConfig;
    128143
    @Nullable private final MtlsProvider mtlsProvider;
    144+
    @Nullable private final SecureSessionAgent s2aConfigProvider;
    129145
    @VisibleForTesting final Map<String, String> headersWithDuplicatesRemoved = new HashMap<>();
    130146

    131147
    @Nullable
    @@ -136,7 +152,9 @@ private InstantiatingGrpcChannelProvider(Builder builder) {
    136152
    this.executor = builder.executor;
    137153
    this.headerProvider = builder.headerProvider;
    138154
    this.endpoint = builder.endpoint;
    155+
    this.useS2A = builder.useS2A;
    139156
    this.mtlsProvider = builder.mtlsProvider;
    157+
    this.s2aConfigProvider = builder.s2aConfigProvider;
    140158
    this.envProvider = builder.envProvider;
    141159
    this.interceptorProvider = builder.interceptorProvider;
    142160
    this.maxInboundMessageSize = builder.maxInboundMessageSize;
    @@ -225,6 +243,17 @@ public TransportChannelProvider withEndpoint(String endpoint) {
    225243
    return toBuilder().setEndpoint(endpoint).build();
    226244
    }
    227245

    246+
    /**
    247+
    * Specify whether or not to use S2A.
    248+
    *
    249+
    * @param useS2A
    250+
    * @return A new {@link InstantiatingGrpcChannelProvider} with useS2A set.
    251+
    */
    252+
    @Override
    253+
    public TransportChannelProvider withUseS2A(boolean useS2A) {
    254+
    return toBuilder().setUseS2A(useS2A).build();
    255+
    }
    256+
    228257
    /** @deprecated Please modify pool settings via {@link #toBuilder()} */
    229258
    @Deprecated
    230259
    @Override
    @@ -410,6 +439,101 @@ ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSec
    410439
    return null;
    411440
    }
    412441

    442+
    /**
    443+
    * This method creates {@link TlsChannelCredentials} to be used by the client to establish an mTLS
    444+
    * connection to S2A. Returns null if any of {@param trustBundle}, {@param privateKey} or {@param
    445+
    * certChain} are missing.
    446+
    *
    447+
    * @param trustBundle the trust bundle to be used to establish the client -> S2A mTLS connection
    448+
    * @param privateKey the client's private key to be used to establish the client -> S2A mtls
    449+
    * connection
    450+
    * @param certChain the client's cert chain to be used to establish the client -> S2A mtls
    451+
    * connection
    452+
    * @return {@link ChannelCredentials} to use to create an mtls connection between client and S2A
    453+
    * @throws IOException on error
    454+
    */
    455+
    @VisibleForTesting
    456+
    ChannelCredentials createMtlsToS2AChannelCredentials(
    457+
    File trustBundle, File privateKey, File certChain) throws IOException {
    458+
    if (trustBundle == null || privateKey == null || certChain == null) {
    459+
    return null;
    460+
    }
    461+
    return TlsChannelCredentials.newBuilder()
    462+
    .keyManager(privateKey, certChain)
    463+
    .trustManager(trustBundle)
    464+
    .build();
    465+
    }
    466+
    467+
    /**
    468+
    * This method creates {@link ChannelCredentials} to be used by client to establish a plaintext
    469+
    * connection to S2A. if {@param plaintextAddress} is not present, returns null.
    470+
    *
    471+
    * @param plaintextAddress the address to reach S2A which accepts plaintext connections
    472+
    * @return {@link ChannelCredentials} to use to create a plaintext connection between client and
    473+
    * S2A
    474+
    */
    475+
    ChannelCredentials createPlaintextToS2AChannelCredentials(String plaintextAddress) {
    476+
    if (Strings.isNullOrEmpty(plaintextAddress)) {
    477+
    return null;
    478+
    }
    479+
    return S2AChannelCredentials.newBuilder(plaintextAddress, InsecureChannelCredentials.create())
    480+
    .build();
    481+
    }
    482+
    483+
    /**
    484+
    * This method creates gRPC {@link ChannelCredentials} configured to use S2A to estbalish a mTLS
    485+
    * connection. First, the address of S2A is discovered by using the {@link S2A} utility to learn
    486+
    * the {@code mtlsAddress} to reach S2A and the {@code plaintextAddress} to reach S2A. Prefer to
    487+
    * use the {@code mtlsAddress} address to reach S2A if it is non-empty and the MTLS-MDS
    488+
    * credentials can successfully be discovered and used to create {@link TlsChannelCredentials}. If
    489+
    * there is any failure using mTLS-to-S2A, fallback to using a plaintext connection to S2A using
    490+
    * the {@code plaintextAddress}. If {@code plaintextAddress} is not available, this function
    491+
    * returns null; in this case S2A will not be used, and a TLS connection to the service will be
    492+
    * established.
    493+
    *
    494+
    * @return {@link ChannelCredentials} configured to use S2A to create mTLS connection to
    495+
    * mtlsEndpoint.
    496+
    */
    497+
    ChannelCredentials createS2ASecuredChannelCredentials() {
    498+
    SecureSessionAgentConfig config = s2aConfigProvider.getConfig();
    499+
    String plaintextAddress = config.getPlaintextAddress();
    500+
    String mtlsAddress = config.getMtlsAddress();
    501+
    if (Strings.isNullOrEmpty(mtlsAddress)) {
    502+
    // Fallback to plaintext connection to S2A.
    503+
    LOG.log(
    504+
    Level.INFO,
    505+
    "Cannot establish an mTLS connection to S2A because autoconfig endpoint did not return a mtls address to reach S2A.");
    506+
    return createPlaintextToS2AChannelCredentials(plaintextAddress);
    507+
    }
    508+
    // Currently, MTLS to MDS is only available on GCE. See:
    509+
    // https://cloud.google.com/compute/docs/metadata/overview#https-mds
    510+
    // Try to load MTLS-MDS creds.
    511+
    File rootFile = new File(MTLS_MDS_ROOT_PATH);
    512+
    File certKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY_PATH);
    513+
    if (rootFile.isFile() && certKeyFile.isFile()) {
    514+
    // Try to connect to S2A using mTLS.
    515+
    ChannelCredentials mtlsToS2AChannelCredentials = null;
    516+
    try {
    517+
    mtlsToS2AChannelCredentials =
    518+
    createMtlsToS2AChannelCredentials(rootFile, certKeyFile, certKeyFile);
    519+
    } catch (IOException ignore) {
    520+
    // Fallback to plaintext-to-S2A connection on error.
    521+
    LOG.log(
    522+
    Level.WARNING,
    523+
    "Cannot establish an mTLS connection to S2A due to error creating MTLS to MDS TlsChannelCredentials credentials, falling back to plaintext connection to S2A: "
    524+
    + ignore.getMessage());
    525+
    return createPlaintextToS2AChannelCredentials(plaintextAddress);
    526+
    }
    527+
    return S2AChannelCredentials.newBuilder(mtlsAddress, mtlsToS2AChannelCredentials).build();
    528+
    } else {
    529+
    // Fallback to plaintext-to-S2A connection if MTLS-MDS creds do not exist.
    530+
    LOG.log(
    531+
    Level.INFO,
    532+
    "Cannot establish an mTLS connection to S2A because MTLS to MDS credentials do not exist on filesystem, falling back to plaintext connection to S2A");
    533+
    return createPlaintextToS2AChannelCredentials(plaintextAddress);
    534+
    }
    535+
    }
    536+
    413537
    private ManagedChannel createSingleChannel() throws IOException {
    414538
    GrpcHeaderInterceptor headerInterceptor =
    415539
    new GrpcHeaderInterceptor(headersWithDuplicatesRemoved);
    @@ -447,16 +571,31 @@ private ManagedChannel createSingleChannel() throws IOException {
    447571
    builder.keepAliveTime(DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS);
    448572
    builder.keepAliveTimeout(DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
    449573
    } else {
    574+
    // Try and create credentials via DCA. See https://google.aip.dev/auth/4114.
    450575
    ChannelCredentials channelCredentials;
    451576
    try {
    452577
    channelCredentials = createMtlsChannelCredentials();
    453578
    } catch (GeneralSecurityException e) {
    454579
    throw new IOException(e);
    455580
    }
    456581
    if (channelCredentials != null) {
    582+
    // Create the channel using channel credentials created via DCA.
    457583
    builder = Grpc.newChannelBuilder(endpoint, channelCredentials);
    458584
    } else {
    459-
    builder = ManagedChannelBuilder.forAddress(serviceAddress, port);
    585+
    // Could not create channel credentials via DCA. In accordance with
    586+
    // https://google.aip.dev/auth/4115, if credentials not available through
    587+
    // DCA, try mTLS with credentials held by the S2A (Secure Session Agent).
    588+
    if (useS2A) {
    589+
    channelCredentials = createS2ASecuredChannelCredentials();
    590+
    }
    591+
    if (channelCredentials != null) {
    592+
    // Create the channel using S2A-secured channel credentials.
    593+
    // {@code endpoint} is set to mtlsEndpoint in {@link EndpointContext} when useS2A is true.
    594+
    builder = Grpc.newChannelBuilder(endpoint, channelCredentials);
    595+
    } else {
    596+
    // Use default if we cannot initialize channel credentials via DCA or S2A.
    597+
    builder = ManagedChannelBuilder.forAddress(serviceAddress, port);
    598+
    }
    460599
    }
    461600
    }
    462601
    // google-c2p resolver requires service config lookup
    @@ -604,7 +743,9 @@ public static final class Builder {
    604743
    private Executor executor;
    605744
    private HeaderProvider headerProvider;
    606745
    private String endpoint;
    746+
    private boolean useS2A;
    607747
    private EnvironmentProvider envProvider;
    748+
    private SecureSessionAgent s2aConfigProvider = SecureSessionAgent.create();
    608749
    private MtlsProvider mtlsProvider = new MtlsProvider();
    609750
    @Nullable private GrpcInterceptorProvider interceptorProvider;
    610751
    @Nullable private Integer maxInboundMessageSize;
    @@ -632,6 +773,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) {
    632773
    this.executor = provider.executor;
    633774
    this.headerProvider = provider.headerProvider;
    634775
    this.endpoint = provider.endpoint;
    776+
    this.useS2A = provider.useS2A;
    635777
    this.envProvider = provider.envProvider;
    636778
    this.interceptorProvider = provider.interceptorProvider;
    637779
    this.maxInboundMessageSize = provider.maxInboundMessageSize;
    @@ -648,6 +790,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) {
    648790
    this.allowNonDefaultServiceAccount = provider.allowNonDefaultServiceAccount;
    649791
    this.directPathServiceConfig = provider.directPathServiceConfig;
    650792
    this.mtlsProvider = provider.mtlsProvider;
    793+
    this.s2aConfigProvider = provider.s2aConfigProvider;
    651794
    }
    652795

    653796
    /**
    @@ -700,12 +843,23 @@ public Builder setEndpoint(String endpoint) {
    700843
    return this;
    701844
    }
    702845

    846+
    Builder setUseS2A(boolean useS2A) {
    847+
    this.useS2A = useS2A;
    848+
    return this;
    849+
    }
    850+
    703851
    @VisibleForTesting
    704852
    Builder setMtlsProvider(MtlsProvider mtlsProvider) {
    705853
    this.mtlsProvider = mtlsProvider;
    706854
    return this;
    707855
    }
    708856

    857+
    @VisibleForTesting
    858+
    Builder setS2AConfigProvider(SecureSessionAgent s2aConfigProvider) {
    859+
    this.s2aConfigProvider = s2aConfigProvider;
    860+
    return this;
    861+
    }
    862+
    709863
    /**
    710864
    * Sets the GrpcInterceptorProvider for this TransportChannelProvider.
    711865
    *

    gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLongRunningTest.java

    Lines changed: 2 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -101,6 +101,8 @@ void setUp() throws IOException {
    101101
    TransportChannel transportChannel =
    102102
    GrpcTransportChannel.newBuilder().setManagedChannel(channel).build();
    103103
    when(operationsChannelProvider.getTransportChannel()).thenReturn(transportChannel);
    104+
    when(operationsChannelProvider.withUseS2A(Mockito.any(boolean.class)))
    105+
    .thenReturn(operationsChannelProvider);
    104106

    105107
    clock = new FakeApiClock(0L);
    106108
    executor = RecordingScheduler.create(clock);

    0 commit comments

    Comments
     (0)
    0