Skip to content

Commit 7bdfc96

Browse files
Add support for mTLS authentication in Arrow Flight client
1 parent c21e043 commit 7bdfc96

File tree

11 files changed

+395
-11
lines changed

11 files changed

+395
-11
lines changed

presto-base-arrow-flight/src/main/java/com/facebook/plugin/arrow/ArrowFlightConfig.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public class ArrowFlightConfig
2020
private String server;
2121
private boolean verifyServer = true;
2222
private String flightServerSSLCertificate;
23+
private String flightClientSSLCertificate;
24+
private String flightClientSSLKey;
2325
private boolean arrowFlightServerSslEnabled;
2426
private Integer arrowFlightPort;
2527

@@ -82,4 +84,28 @@ public ArrowFlightConfig setArrowFlightServerSslEnabled(boolean arrowFlightServe
8284
this.arrowFlightServerSslEnabled = arrowFlightServerSslEnabled;
8385
return this;
8486
}
87+
88+
public String getFlightClientSSLCertificate()
89+
{
90+
return flightClientSSLCertificate;
91+
}
92+
93+
@Config("arrow-flight.client-ssl-certificate")
94+
public ArrowFlightConfig setFlightClientSSLCertificate(String flightClientSSLCertificate)
95+
{
96+
this.flightClientSSLCertificate = flightClientSSLCertificate;
97+
return this;
98+
}
99+
100+
public String getFlightClientSSLKey()
101+
{
102+
return flightClientSSLKey;
103+
}
104+
105+
@Config("arrow-flight.client-ssl-key")
106+
public ArrowFlightConfig setFlightClientSSLKey(String flightClientSSLKey)
107+
{
108+
this.flightClientSSLKey = flightClientSSLKey;
109+
return this;
110+
}
85111
}

presto-base-arrow-flight/src/main/java/com/facebook/plugin/arrow/BaseArrowFlightClientHandler.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,24 @@ protected FlightClient createFlightClient(Location location)
7373
flightClientBuilder.trustedCertificates(trustedCertificate.get()).useTls();
7474
}
7575

76+
Optional<InputStream> clientCertificate = Optional.empty();
77+
Optional<InputStream> clientKey = Optional.empty();
78+
if (config.getFlightClientSSLCertificate() != null && config.getFlightClientSSLKey() != null) {
79+
clientCertificate = Optional.of(newInputStream(Paths.get(config.getFlightClientSSLCertificate())));
80+
clientKey = Optional.of(newInputStream(Paths.get(config.getFlightClientSSLKey())));
81+
flightClientBuilder.clientCertificate(clientCertificate.get(), clientKey.get()).useTls();
82+
}
83+
7684
FlightClient flightClient = flightClientBuilder.build();
7785
if (trustedCertificate.isPresent()) {
7886
trustedCertificate.get().close();
7987
}
88+
if (clientCertificate.isPresent()) {
89+
clientCertificate.get().close();
90+
}
91+
if (clientKey.isPresent()) {
92+
clientKey.get().close();
93+
}
8094

8195
return flightClient;
8296
}

presto-base-arrow-flight/src/test/java/com/facebook/plugin/arrow/ArrowFlightQueryRunner.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,15 @@ public static DistributedQueryRunner createQueryRunner(
6464
Optional<BiFunction<Integer, URI, Process>> externalWorkerLauncher)
6565
throws Exception
6666
{
67-
return createQueryRunner(extraProperties, ImmutableMap.of("arrow-flight.server.port", String.valueOf(flightServerPort)), coordinatorProperties, externalWorkerLauncher);
67+
ImmutableMap.Builder<String, String> catalogProperties = ImmutableMap.<String, String>builder()
68+
.put("arrow-flight.server.port", String.valueOf(flightServerPort))
69+
.put("arrow-flight.server", "localhost")
70+
.put("arrow-flight.server-ssl-enabled", "true")
71+
.put("arrow-flight.server-ssl-certificate", "src/test/resources/server.crt");
72+
return createQueryRunner(extraProperties, catalogProperties.build(), coordinatorProperties, externalWorkerLauncher);
6873
}
6974

70-
private static DistributedQueryRunner createQueryRunner(
75+
public static DistributedQueryRunner createQueryRunner(
7176
Map<String, String> extraProperties,
7277
Map<String, String> catalogProperties,
7378
Map<String, String> coordinatorProperties,
@@ -92,13 +97,7 @@ private static DistributedQueryRunner createQueryRunner(
9297
boolean nativeExecution = externalWorkerLauncher.isPresent();
9398
queryRunner.installPlugin(new TestingArrowFlightPlugin(nativeExecution));
9499

95-
ImmutableMap.Builder<String, String> properties = ImmutableMap.<String, String>builder()
96-
.putAll(catalogProperties)
97-
.put("arrow-flight.server", "localhost")
98-
.put("arrow-flight.server-ssl-enabled", "true")
99-
.put("arrow-flight.server-ssl-certificate", "src/test/resources/server.crt");
100-
101-
queryRunner.createCatalog(ARROW_FLIGHT_CATALOG, ARROW_FLIGHT_CONNECTOR, properties.build());
100+
queryRunner.createCatalog(ARROW_FLIGHT_CATALOG, ARROW_FLIGHT_CONNECTOR, catalogProperties);
102101

103102
return queryRunner;
104103
}
@@ -140,8 +139,8 @@ public static void main(String[] args)
140139
log.info("Server listening on port " + server.getPort());
141140

142141
DistributedQueryRunner queryRunner = createQueryRunner(
142+
9443,
143143
ImmutableMap.of("http-server.http.port", "8080"),
144-
ImmutableMap.of("arrow-flight.server.port", String.valueOf(9443)),
145144
ImmutableMap.of(),
146145
Optional.empty());
147146
Thread.sleep(10);
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.facebook.plugin.arrow;
15+
16+
import com.facebook.airlift.log.Logger;
17+
import com.facebook.plugin.arrow.testingServer.TestingArrowProducer;
18+
import com.facebook.presto.testing.QueryRunner;
19+
import com.facebook.presto.tests.AbstractTestQueryFramework;
20+
import com.facebook.presto.tests.DistributedQueryRunner;
21+
import com.google.common.collect.ImmutableMap;
22+
import org.apache.arrow.flight.CallOption;
23+
import org.apache.arrow.flight.CallOptions;
24+
import org.apache.arrow.flight.FlightServer;
25+
import org.apache.arrow.flight.Location;
26+
import org.apache.arrow.memory.RootAllocator;
27+
import org.testng.annotations.AfterClass;
28+
import org.testng.annotations.BeforeClass;
29+
import org.testng.annotations.Test;
30+
31+
import java.io.File;
32+
import java.io.IOException;
33+
import java.util.Optional;
34+
import java.util.concurrent.TimeUnit;
35+
36+
public class TestArrowFlightMTLS
37+
extends AbstractTestQueryFramework
38+
{
39+
private static final Logger logger = Logger.get(TestArrowFlightMTLS.class);
40+
private static final CallOption CALL_OPTIONS = CallOptions.timeout(300, TimeUnit.SECONDS);
41+
private final int serverPort;
42+
private RootAllocator allocator;
43+
private FlightServer server;
44+
private DistributedQueryRunner arrowFlightQueryRunner;
45+
46+
public TestArrowFlightMTLS()
47+
throws IOException
48+
{
49+
this.serverPort = ArrowFlightQueryRunner.findUnusedPort();
50+
}
51+
52+
@BeforeClass
53+
public void setup()
54+
throws Exception
55+
{
56+
arrowFlightQueryRunner = getDistributedQueryRunner();
57+
File certChainFile = new File("src/test/resources/mtls/server.crt");
58+
File privateKeyFile = new File("src/test/resources/mtls/server.key");
59+
File caCertFile = new File("src/test/resources/mtls/ca.crt");
60+
61+
allocator = new RootAllocator(Long.MAX_VALUE);
62+
63+
Location location = Location.forGrpcTls("localhost", serverPort);
64+
server = FlightServer.builder(allocator, location, new TestingArrowProducer(allocator))
65+
.useTls(certChainFile, privateKeyFile)
66+
.useMTlsClientVerification(caCertFile)
67+
.build();
68+
69+
server.start();
70+
logger.info("Server listening on port %s", server.getPort());
71+
}
72+
73+
@AfterClass(alwaysRun = true)
74+
public void close()
75+
throws InterruptedException
76+
{
77+
arrowFlightQueryRunner.close();
78+
server.close();
79+
allocator.close();
80+
}
81+
82+
@Override
83+
protected QueryRunner createQueryRunner()
84+
throws Exception
85+
{
86+
ImmutableMap.Builder<String, String> catalogProperties = ImmutableMap.<String, String>builder()
87+
.put("arrow-flight.server.port", String.valueOf(serverPort))
88+
.put("arrow-flight.server", "localhost")
89+
.put("arrow-flight.server-ssl-enabled", "true")
90+
.put("arrow-flight.server-ssl-certificate", "src/test/resources/mtls/server.crt")
91+
.put("arrow-flight.client-ssl-certificate", "src/test/resources/mtls/client.crt")
92+
.put("arrow-flight.client-ssl-key", "src/test/resources/mtls/client.key");
93+
return ArrowFlightQueryRunner.createQueryRunner(ImmutableMap.of(), catalogProperties.build(), ImmutableMap.of(), Optional.empty());
94+
}
95+
96+
@Test
97+
public void testMtls()
98+
{
99+
assertQuery("SELECT COUNT(*) FROM orders");
100+
}
101+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.facebook.plugin.arrow;
15+
16+
import com.facebook.airlift.log.Logger;
17+
import com.facebook.plugin.arrow.testingServer.TestingArrowProducer;
18+
import com.facebook.presto.testing.QueryRunner;
19+
import com.facebook.presto.tests.AbstractTestQueryFramework;
20+
import com.facebook.presto.tests.DistributedQueryRunner;
21+
import com.google.common.collect.ImmutableMap;
22+
import org.apache.arrow.flight.CallOption;
23+
import org.apache.arrow.flight.CallOptions;
24+
import org.apache.arrow.flight.FlightServer;
25+
import org.apache.arrow.flight.Location;
26+
import org.apache.arrow.memory.RootAllocator;
27+
import org.testng.annotations.AfterClass;
28+
import org.testng.annotations.BeforeClass;
29+
import org.testng.annotations.Test;
30+
31+
import java.io.File;
32+
import java.io.IOException;
33+
import java.util.Optional;
34+
import java.util.concurrent.TimeUnit;
35+
36+
public class TestArrowFlightMTLSFails
37+
extends AbstractTestQueryFramework
38+
{
39+
private static final Logger logger = Logger.get(TestArrowFlightMTLSFails.class);
40+
private static final CallOption CALL_OPTIONS = CallOptions.timeout(300, TimeUnit.SECONDS);
41+
private final int serverPort;
42+
private RootAllocator allocator;
43+
private FlightServer server;
44+
private DistributedQueryRunner arrowFlightQueryRunner;
45+
46+
public TestArrowFlightMTLSFails()
47+
throws IOException
48+
{
49+
this.serverPort = ArrowFlightQueryRunner.findUnusedPort();
50+
}
51+
52+
@BeforeClass
53+
public void setup()
54+
throws Exception
55+
{
56+
arrowFlightQueryRunner = getDistributedQueryRunner();
57+
File certChainFile = new File("src/test/resources/mtls/server.crt");
58+
File privateKeyFile = new File("src/test/resources/mtls/server.key");
59+
File caCertFile = new File("src/test/resources/mtls/ca.crt");
60+
61+
allocator = new RootAllocator(Long.MAX_VALUE);
62+
63+
Location location = Location.forGrpcTls("localhost", serverPort);
64+
server = FlightServer.builder(allocator, location, new TestingArrowProducer(allocator))
65+
.useTls(certChainFile, privateKeyFile)
66+
.useMTlsClientVerification(caCertFile)
67+
.build();
68+
69+
server.start();
70+
logger.info("Server listening on port %s", server.getPort());
71+
}
72+
73+
@AfterClass(alwaysRun = true)
74+
public void close()
75+
throws InterruptedException
76+
{
77+
arrowFlightQueryRunner.close();
78+
server.close();
79+
allocator.close();
80+
}
81+
82+
@Override
83+
protected QueryRunner createQueryRunner()
84+
throws Exception
85+
{
86+
ImmutableMap.Builder<String, String> catalogProperties = ImmutableMap.<String, String>builder()
87+
.put("arrow-flight.server.port", String.valueOf(serverPort))
88+
.put("arrow-flight.server", "localhost")
89+
.put("arrow-flight.server-ssl-enabled", "true")
90+
.put("arrow-flight.server-ssl-certificate", "src/test/resources/mtls/server.crt");
91+
return ArrowFlightQueryRunner.createQueryRunner(ImmutableMap.of(), catalogProperties.build(), ImmutableMap.of(), Optional.empty());
92+
}
93+
94+
@Test
95+
public void testMtlsFailure()
96+
{
97+
assertQueryFails("SELECT COUNT(*) FROM orders", "ssl exception");
98+
}
99+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFozCCA4ugAwIBAgIUTnZhzGzxAzaTJ5DDQnmBp8jvBUMwDQYJKoZIhvcNAQEL
3+
BQAwYDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
4+
MQ4wDAYDVQQKDAVNeU9yZzEPMA0GA1UECwwGTXlVbml0MREwDwYDVQQDDAhNeVJv
5+
b3RDQTAgFw0yNTA1MjIxNjA2MDNaGA8yMTI1MDQyODE2MDYwM1owYDELMAkGA1UE
6+
BhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MQ4wDAYDVQQKDAVN
7+
eU9yZzEPMA0GA1UECwwGTXlVbml0MREwDwYDVQQDDAhNeVJvb3RDQTCCAiIwDQYJ
8+
KoZIhvcNAQEBBQADggIPADCCAgoCggIBALmNRxPZt9vvFvIQcdCM3xcAOiTepahR
9+
zsK86AwzXbwcWwOw+rQP0iXiMGjP9wJy0C1hU+j7F+gu5+d5j3DK9/iTlIqByjN+
10+
gffsZDSBBZBozJxARBWKaEFKRKbhCmr+v92ZN1KMYhhwc5W3SRp6y3kvsWYM4/JE
11+
pRzYUy0505g3p7PnpQzZZSSzV33+9fn5ggafVB9FSJqOuBKUBTITt+Rf1pNV2ZGC
12+
f2X39KOIIRz2Hz1L6hdqeMQN9V5KQ7C6oPfl+ho97JHaZirtHr1XZJ6YQRy7Mldp
13+
jOPj4MMKqi8m+HhyCSznIDNiMp0OACX3CZ5RmKRYfnunOuw+fvI28HyOXMgfWKa2
14+
o/2/YlAzNrD50hJPwQMTlKWJm2gY2n5x2FWT6/8aXeCnsJALK6Dj6Ax0wxkAFfIo
15+
76REHlXX2fIiz0cciYwYtvwhjp5efqX22B7LDkhu7fJ42yUd6g3crbmGM8OOoXgL
16+
w3MWx30FatTyDT8un2ZvVDJEADW3+WWtyrZWHFMVFrVnN7Di4MAuWsRIVtuO6PEV
17+
6pPS55NBmvKzWAoYBmpH103GlIxvZ6CCTeKqFbcrI77smrIO5CLmzl5yjb/urmy4
18+
GLYHM+EPB5pJKQO8g6fyM369mhEjBxt/RJGv9E0Jw7KmnHn5V2qSn4Yi41dUp7Cp
19+
pQVIaYlJGn65AgMBAAGjUzBRMB0GA1UdDgQWBBRjt9KoJO8CO/Cy/xTJztrbdUlv
20+
9DAfBgNVHSMEGDAWgBRjt9KoJO8CO/Cy/xTJztrbdUlv9DAPBgNVHRMBAf8EBTAD
21+
AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQApizBpSaZDYjBhOxD6+B5em18OIf5ZIjHk
22+
iAhuppR+HaqyAHJgCO707dZVmFz9ECUZI9mvKcmj+h0Wh/mK4cSiDunFB9yUr67U
23+
wV5F2/u/JAAq6VsbdrDiZPUwET8U9ai14LMEgPPF+Zif+wnopRav5lbiPoJVUjqr
24+
wVoP2AHijIP46YCWwXqOTJMC79ccUMBZeDwF4bOquIADLmEnANp6fiMI+eE6OLFs
25+
fDtjFqybRUZqzewv2lpzH2ZYEYk4bIk76TGkOYtrwJ+iQj77ZZFSBW5zkry/zaG3
26+
/5Ufjv65T9Zr1jmIMigcmCHwNsCLOYzIKjRaiuLGs4B9s8SGEauTRhP0dG5ndWPw
27+
50NeSNJr37MHdKky44WAFlAk9BAKlghOaC5m2RyMof8DwYKPEe5epe1wBotiPqSX
28+
doaZvch6wkuo8xvFKqH6rBTWJLMwuFt7m3XrGqGYlE+1gvuEfn7ZGzG00sl218mZ
29+
MfsLqJfft92ARC1/qJvUFr5mM6SV4eQeTl1tAtv6Xfczr+3/iqc5gbeG25dXQclO
30+
y1qIKthAoXFq6rAZ+bvfASiVV1OQS76nWSiYYS1dDPQJ/g4aawOkUYr9OjS5HNQ+
31+
rNAcLB+I0oaZQzZ85098qAVAJ76eFATb4ieDK6m0j6Fq5ddwrlYzxEEr7TscfHW2
32+
zPCtinUe0w==
33+
-----END CERTIFICATE-----
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEkDCCAnigAwIBAgIUUIByH9V7DhUf5n3Qd7tPxpPixQYwDQYJKoZIhvcNAQEL
3+
BQAwYDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
4+
MQ4wDAYDVQQKDAVNeU9yZzEPMA0GA1UECwwGTXlVbml0MREwDwYDVQQDDAhNeVJv
5+
b3RDQTAgFw0yNTA1MjIxNjA4MzFaGA8yMTI1MDQyODE2MDgzMVowXjELMAkGA1UE
6+
BhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MQ4wDAYDVQQKDAVN
7+
eU9yZzEPMA0GA1UECwwGTXlVbml0MQ8wDQYDVQQDDAZjbGllbnQwggEiMA0GCSqG
8+
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8DK/RdBF6I+k+DMGrjhMMBCnpPNwJtzJU
9+
uXYcHFYEdBnHY/rpjk/fi+7jD8bppynCZPakrDX+5VIMzS4HBU/CHY26eR2ItiWq
10+
DoDkPAlCdgeKIGNYYEvVSuUW5YQX6fuD8PfCpCP5zK7DJC2xTTsyEjBzD+MnIB7T
11+
ja9/22Djo2Ib2l9BEBOD+k79caPFtSqDQVdS5JLJ/P7BeqGuFS8bEgtLwwCzRxPK
12+
kG64rXb9F0IErGwjXi/70BA24EW0uGAzzeY5Pnlx5MulEIjuxII/dTuoo+/uirN0
13+
wxURKSzTMyzzoJqGX9Ng9+Z+VqWFrnqckcmFcD4T0sPoHpWZsYIHAgMBAAGjQjBA
14+
MB0GA1UdDgQWBBRW/6j5rNwnwTBAXY5igXm3ETzv/DAfBgNVHSMEGDAWgBRjt9Ko
15+
JO8CO/Cy/xTJztrbdUlv9DANBgkqhkiG9w0BAQsFAAOCAgEAVLpfZDgkL1Dz/+Fq
16+
vSl2IoxOFNd2DTa6yM8/1wpvMVTA024lp0ttyoz0o1621hTRexcXTimZqUNAtPV6
17+
Gwmb2ACLN4XtLk9QT9XjDtWKzPxCJ+ze7rrhj1jYqv9yUebdkJoMKfcbwYi0gtpt
18+
HlaJqNoKgzZxOCGhTtdS3ypb9nDCyx3fmFk5mIYfzEszoMmqNL006ANlJ0IKkFZj
19+
vUkkFyMGLerInmTDRjDLkUCkNKaJUYjZhf/FNwVtc1A9a/bDJMEYVog+CY0dpXKb
20+
1IGaXzB4ewhuQKuhb/LCZT1pNm/cGCY2cRGFy9EVAuZ5FV0ajh1HYwdpGDzucoUD
21+
UaTHIK40E2/kZorJa37Xyn7Lekgun6YpfOudBkKg5mlV6qoL9W/lTZPjMHs2ufvW
22+
/A5S4okR4JmhC44TMgAv90MU9yEP90OkzW6egatBShWySJ3Bn5W+ebQSwJ38wgTy
23+
e6j5jWh7xiiPC4TJbSXVMGEfJw/c2hx4R/83MqBhVLPfoapaUCDUWniv6n7zl5ML
24+
k7WIZzXSK212/H+eVXFJ1Gq6zztOPkN9QGgr+dbsCzdLWPJzZDmI7+lgT2EKxIV/
25+
VMtHVOe2bLkePTNA2+vXQhs0p7JDtzyATAyMdhJwPljt8X+HJxEoAP4Dk6PcK3Bd
26+
E92yOuW2jl6FzgqKqtVQvxIiBgc=
27+
-----END CERTIFICATE-----
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8DK/RdBF6I+k+
3+
DMGrjhMMBCnpPNwJtzJUuXYcHFYEdBnHY/rpjk/fi+7jD8bppynCZPakrDX+5VIM
4+
zS4HBU/CHY26eR2ItiWqDoDkPAlCdgeKIGNYYEvVSuUW5YQX6fuD8PfCpCP5zK7D
5+
JC2xTTsyEjBzD+MnIB7Tja9/22Djo2Ib2l9BEBOD+k79caPFtSqDQVdS5JLJ/P7B
6+
eqGuFS8bEgtLwwCzRxPKkG64rXb9F0IErGwjXi/70BA24EW0uGAzzeY5Pnlx5Mul
7+
EIjuxII/dTuoo+/uirN0wxURKSzTMyzzoJqGX9Ng9+Z+VqWFrnqckcmFcD4T0sPo
8+
HpWZsYIHAgMBAAECggEAViw6JXFa0O3D5HtUBJmGgOsniYoqCwm4NrsGNLuHb2ME
9+
rSpTwNNGJtqpDcQdEtVXfY1muO9xjuznPJaJkQ4ODpYcbGcz8YIGoHck+XHJjHsp
10+
2VIeNFFsbsFzWZqzfYHrj/rMjpVJJx90tlfN2IHbroZHTXLqVPOTLL6wvZZ6P9XF
11+
zqpWABKOaEDbenhSFFZeF5KR3NG9HSTm4YLuekumkH+QgrveDfDwXG4hAHqg836o
12+
OF3NPaij6VlSR18nuyW0wMs/Ceu13P+GALqHmz98pFyVgHWQFryL9IccvJQDyEnt
13+
saeG4IAVlJbZDGTnRgANLhpwBr7XhMG1aK+wmOMRgQKBgQDkcatiATlr9L8gfnHb
14+
6pmX//AZLdXuQLXfuTvu638Brhm770noLgfIC+HIp5kCHxT2Xj5Vn+MSnYD6R6Wh
15+
chApRKJUdsuz1iOq23YJjvsSLWCGpl9IxR7WY27uGOPIjQcOd1PRbkCq9AgUJwyn
16+
ryca3sbYh/XQOWGLbJNIQs/S/QKBgQDSu6PVeMaS3276KblvGIvvaSAQDQWxXcC+
17+
sA4CBmvjzx3xx5GAox/w7tcKmK/KQxNhaYy6N7xLc1YUJ9FbnT2PZQJhtP2d2Gat
18+
Zre/+Qa+u84cR5hj9EI+B8FjW7D/psEj16KjHCds/SET6ngPM+RdB4N9daVFCurt
19+
p0f717yiUwKBgBTJDun06I+dDkLbnmp/FwiQff0cgYmTE7lOdliPzteNSsQhypy4
20+
i3a1Ng72yOI7h8G+43cQ/C02bYTYPgbJhRTsLMT4piIvysECBORrwQZvYIf/3U2W
21+
ue6Rz4cUdq1Jv6meS98TZAjp+U40G1+qfSlhub/75u7SOcDg2SnLAnPVAoGBAIOO
22+
EmRE5qpwA+b2P0Ykq89E8Hg0uPYWEiq427XV7mqkNQxoSuRkcZ9Ga0a5NRzurN2m
23+
N+1UuB7eHMGubdtkmTa4lzkJ9T4iB09/DX0x6E0QD0bGR1M2/FefHdJ6PlAK+Q34
24+
Ixbyj4ZRq+G0AUl0Wr7c3vBmjktA2pKMWLrW3nLzAoGBAKTl7qX6CD42gAJuT5Hp
25+
rrXqlppVIyRvuXzXtX/Xq81IUHlBgS/t9HPyqDzmTKfxD8540kI+15bWPDHSJxiQ
26+
ccqPaKyXhBXstDwGmlPKVzJUxk0dz5NHs+8gItUDOg78pM3siXN7vW9XBCH7mCDA
27+
4zet/C0YCAiFVT+ipMoXy8Nc
28+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)