[7.x] Fix ReloadSecureSettings API to consume password (#54771) (#55059)

The secure_settings_password was never taken into consideration in
the ReloadSecureSettings API. This commit fixes that and adds
necessary REST layer testing. Doing so, it also:

- Allows TestClusters to have a password protected keystore
so that it can be set for tests.
- Adds a parameter to the run task so that elastisearch can
be run with a password protected keystore from source.
This commit is contained in:
Ioannis Kakavas 2020-04-13 09:50:55 +03:00 committed by GitHub
parent 862799956c
commit 7a8a66d9ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 351 additions and 38 deletions

View File

@ -408,6 +408,14 @@ class Run extends DefaultTask {
public void setDataDir(String dataDirStr) { public void setDataDir(String dataDirStr) {
project.project(':distribution').run.dataDir = dataDirStr project.project(':distribution').run.dataDir = dataDirStr
} }
@Option(
option = "keystore-password",
description = "Set the elasticsearch keystore password"
)
public void setKeystorePassword(String password) {
project.project(':distribution').run.keystorePassword = password
}
} }
task run(type: Run) { task run(type: Run) {

View File

@ -174,6 +174,11 @@ public class ElasticsearchCluster implements TestClusterConfiguration, Named {
nodes.all(each -> each.keystore(key, valueSupplier)); nodes.all(each -> each.keystore(key, valueSupplier));
} }
@Override
public void keystorePassword(String password) {
nodes.all(each -> each.keystorePassword(password));
}
@Override @Override
public void cliSetup(String binTool, CharSequence... args) { public void cliSetup(String binTool, CharSequence... args) {
nodes.all(each -> each.cliSetup(binTool, args)); nodes.all(each -> each.cliSetup(binTool, args));

View File

@ -143,6 +143,7 @@ public class ElasticsearchNode implements TestClusterConfiguration {
private final Path httpPortsFile; private final Path httpPortsFile;
private final Path esStdoutFile; private final Path esStdoutFile;
private final Path esStderrFile; private final Path esStderrFile;
private final Path esStdinFile;
private final Path tmpDir; private final Path tmpDir;
private int currentDistro = 0; private int currentDistro = 0;
@ -154,6 +155,7 @@ public class ElasticsearchNode implements TestClusterConfiguration {
private String httpPort = "0"; private String httpPort = "0";
private String transportPort = "0"; private String transportPort = "0";
private Path confPathData; private Path confPathData;
private String keystorePassword = "";
ElasticsearchNode(String name, Project project, ReaperService reaper, File workingDirBase, Jdk bwcJdk) { ElasticsearchNode(String name, Project project, ReaperService reaper, File workingDirBase, Jdk bwcJdk) {
this.path = project.getPath(); this.path = project.getPath();
@ -170,6 +172,7 @@ public class ElasticsearchNode implements TestClusterConfiguration {
httpPortsFile = confPathLogs.resolve("http.ports"); httpPortsFile = confPathLogs.resolve("http.ports");
esStdoutFile = confPathLogs.resolve("es.stdout.log"); esStdoutFile = confPathLogs.resolve("es.stdout.log");
esStderrFile = confPathLogs.resolve("es.stderr.log"); esStderrFile = confPathLogs.resolve("es.stderr.log");
esStdinFile = workingDir.resolve("es.stdin");
tmpDir = workingDir.resolve("tmp"); tmpDir = workingDir.resolve("tmp");
waitConditions.put("ports files", this::checkPortsFilesExistWithDelay); waitConditions.put("ports files", this::checkPortsFilesExistWithDelay);
@ -305,6 +308,11 @@ public class ElasticsearchNode implements TestClusterConfiguration {
keystoreFiles.put(key, valueSupplier); keystoreFiles.put(key, valueSupplier);
} }
@Override
public void keystorePassword(String password) {
keystorePassword = password;
}
@Override @Override
public void cliSetup(String binTool, CharSequence... args) { public void cliSetup(String binTool, CharSequence... args) {
cliSetup.add(new CliEntry(binTool, args)); cliSetup.add(new CliEntry(binTool, args));
@ -439,13 +447,17 @@ public class ElasticsearchNode implements TestClusterConfiguration {
} }
} }
logToProcessStdout("Creating elasticsearch keystore with password set to [" + keystorePassword + "]");
if (keystorePassword.length() > 0) {
runElasticsearchBinScriptWithInput(keystorePassword + "\n" + keystorePassword, "elasticsearch-keystore", "create", "-p");
} else {
runElasticsearchBinScript("elasticsearch-keystore", "create");
}
if (keystoreSettings.isEmpty() == false || keystoreFiles.isEmpty() == false) { if (keystoreSettings.isEmpty() == false || keystoreFiles.isEmpty() == false) {
logToProcessStdout("Adding " + keystoreSettings.size() + " keystore settings and " + keystoreFiles.size() + " keystore files"); logToProcessStdout("Adding " + keystoreSettings.size() + " keystore settings and " + keystoreFiles.size() + " keystore files");
runElasticsearchBinScript("elasticsearch-keystore", "create");
keystoreSettings.forEach( keystoreSettings.forEach((key, value) -> runKeystoreCommandWithPassword(keystorePassword, value.toString(), "add", "-x", key));
(key, value) -> runElasticsearchBinScriptWithInput(value.toString(), "elasticsearch-keystore", "add", "-x", key)
);
for (Map.Entry<String, File> entry : keystoreFiles.entrySet()) { for (Map.Entry<String, File> entry : keystoreFiles.entrySet()) {
File file = entry.getValue(); File file = entry.getValue();
@ -453,7 +465,7 @@ public class ElasticsearchNode implements TestClusterConfiguration {
if (file.exists() == false) { if (file.exists() == false) {
throw new TestClustersException("supplied keystore file " + file + " does not exist, require for " + this); throw new TestClustersException("supplied keystore file " + file + " does not exist, require for " + this);
} }
runElasticsearchBinScript("elasticsearch-keystore", "add-file", entry.getKey(), file.getAbsolutePath()); runKeystoreCommandWithPassword(keystorePassword, "", "add-file", entry.getKey(), file.getAbsolutePath());
} }
} }
@ -657,6 +669,11 @@ public class ElasticsearchNode implements TestClusterConfiguration {
} }
} }
private void runKeystoreCommandWithPassword(String keystorePassword, String input, CharSequence... args) {
final String actualInput = keystorePassword.length() > 0 ? keystorePassword + "\n" + input : input;
runElasticsearchBinScriptWithInput(actualInput, "elasticsearch-keystore", args);
}
private void runElasticsearchBinScript(String tool, CharSequence... args) { private void runElasticsearchBinScript(String tool, CharSequence... args) {
runElasticsearchBinScriptWithInput("", tool, args); runElasticsearchBinScriptWithInput("", tool, args);
} }
@ -746,6 +763,14 @@ public class ElasticsearchNode implements TestClusterConfiguration {
processBuilder.redirectError(ProcessBuilder.Redirect.appendTo(esStderrFile.toFile())); processBuilder.redirectError(ProcessBuilder.Redirect.appendTo(esStderrFile.toFile()));
processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(esStdoutFile.toFile())); processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(esStdoutFile.toFile()));
if (keystorePassword != null && keystorePassword.length() > 0) {
try {
Files.write(esStdinFile, (keystorePassword + "\n").getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
processBuilder.redirectInput(esStdinFile.toFile());
} catch (IOException e) {
throw new TestClustersException("Failed to set the keystore password for " + this, e);
}
}
LOGGER.info("Running `{}` in `{}` for {} env: {}", command, workingDir, this, environment); LOGGER.info("Running `{}` in `{}` for {} env: {}", command, workingDir, this, environment);
try { try {
esProcess = processBuilder.start(); esProcess = processBuilder.start();

View File

@ -28,6 +28,8 @@ public class RunTask extends DefaultTestClustersTask {
private Path dataDir = null; private Path dataDir = null;
private String keystorePassword = "";
@Option(option = "debug-jvm", description = "Enable debugging configuration, to allow attaching a debugger to elasticsearch.") @Option(option = "debug-jvm", description = "Enable debugging configuration, to allow attaching a debugger to elasticsearch.")
public void setDebug(boolean enabled) { public void setDebug(boolean enabled) {
this.debug = enabled; this.debug = enabled;
@ -43,6 +45,17 @@ public class RunTask extends DefaultTestClustersTask {
dataDir = Paths.get(dataDirStr).toAbsolutePath(); dataDir = Paths.get(dataDirStr).toAbsolutePath();
} }
@Option(option = "keystore-password", description = "Set the elasticsearch keystore password")
public void setKeystorePassword(String password) {
keystorePassword = password;
}
@Input
@Optional
public String getKeystorePassword() {
return keystorePassword;
}
@Input @Input
@Optional @Optional
public String getDataDir() { public String getDataDir() {
@ -90,6 +103,9 @@ public class RunTask extends DefaultTestClustersTask {
node.jvmArgs("-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=" + debugPort); node.jvmArgs("-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=" + debugPort);
debugPort += 1; debugPort += 1;
} }
if (keystorePassword.length() > 0) {
node.keystorePassword(keystorePassword);
}
} }
} }
} }

View File

@ -57,6 +57,8 @@ public interface TestClusterConfiguration {
void keystore(String key, FileSupplier valueSupplier); void keystore(String key, FileSupplier valueSupplier);
void keystorePassword(String password);
void cliSetup(String binTool, CharSequence... args); void cliSetup(String binTool, CharSequence... args);
void setting(String key, String value); void setting(String key, String value);

View File

@ -58,6 +58,7 @@ testClusters.integTest {
} }
setting 'xpack.autoscaling.enabled', 'true' setting 'xpack.autoscaling.enabled', 'true'
setting 'xpack.eql.enabled', 'true' setting 'xpack.eql.enabled', 'true'
keystorePassword 's3cr3t'
} }
// enable regexes in painless so our tests don't complain about example snippets that use them // enable regexes in painless so our tests don't complain about example snippets that use them

View File

@ -36,7 +36,7 @@ the node-specific {es} keystore password.
(Optional, string) The names of particular nodes in the cluster to target. (Optional, string) The names of particular nodes in the cluster to target.
For example, `nodeId1,nodeId2`. For node selection options, see For example, `nodeId1,nodeId2`. For node selection options, see
<<cluster-nodes>>. <<cluster-nodes>>.
NOTE: {es} requires consistent secure settings across the cluster nodes, but NOTE: {es} requires consistent secure settings across the cluster nodes, but
this consistency is not enforced. Hence, reloading specific nodes is not this consistency is not enforced. Hence, reloading specific nodes is not
standard. It is justifiable only when retrying failed reload operations. standard. It is justifiable only when retrying failed reload operations.
@ -44,16 +44,25 @@ standard. It is justifiable only when retrying failed reload operations.
[[cluster-nodes-reload-secure-settings-api-request-body]] [[cluster-nodes-reload-secure-settings-api-request-body]]
==== {api-request-body-title} ==== {api-request-body-title}
`reload_secure_settings`:: `secure_settings_password`::
(Optional, string) The password for the {es} keystore. (Optional, string) The password for the {es} keystore.
[[cluster-nodes-reload-secure-settings-api-example]] [[cluster-nodes-reload-secure-settings-api-example]]
==== {api-examples-title} ==== {api-examples-title}
The following examples assume a common password for the {es} keystore on every
node of the cluster:
[source,console] [source,console]
-------------------------------------------------- --------------------------------------------------
POST _nodes/reload_secure_settings POST _nodes/reload_secure_settings
{
"secure_settings_password":"s3cr3t"
}
POST _nodes/nodeId1,nodeId2/reload_secure_settings POST _nodes/nodeId1,nodeId2/reload_secure_settings
{
"secure_settings_password":"s3cr3t"
}
-------------------------------------------------- --------------------------------------------------
// TEST[setup:node] // TEST[setup:node]
// TEST[s/nodeId1,nodeId2/*/] // TEST[s/nodeId1,nodeId2/*/]
@ -81,27 +90,3 @@ that was thrown during the reload process, if any.
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/"my_cluster"/$body.cluster_name/] // TESTRESPONSE[s/"my_cluster"/$body.cluster_name/]
// TESTRESPONSE[s/"pQHNt5rXTTWNvUgOrdynKg"/\$node_name/] // TESTRESPONSE[s/"pQHNt5rXTTWNvUgOrdynKg"/\$node_name/]
The following example uses a common password for the {es} keystore on every
node of the cluster:
[source,js]
--------------------------------------------------
POST _nodes/reload_secure_settings
{
"reload_secure_settings": "s3cr3t"
}
--------------------------------------------------
// NOTCONSOLE
The following example uses a password for the {es} keystore on the local node:
[source,js]
--------------------------------------------------
POST _nodes/_local/reload_secure_settings
{
"reload_secure_settings": "s3cr3t"
}
--------------------------------------------------
// NOTCONSOLE

View File

@ -35,7 +35,7 @@ using the `bin/elasticsearch-keystore add` command, call:
---- ----
POST _nodes/reload_secure_settings POST _nodes/reload_secure_settings
{ {
"reload_secure_settings": "s3cr3t" <1> "secure_settings_password": "s3cr3t" <1>
} }
---- ----
// NOTCONSOLE // NOTCONSOLE

View File

@ -27,11 +27,15 @@
} }
] ]
}, },
"params":{ "params": {
"timeout":{ "timeout": {
"type":"time", "type": "time",
"description":"Explicit operation timeout" "description": "Explicit operation timeout"
} }
},
"body": {
"description": "An object containing the password for the elasticsearch keystore",
"required": false
} }
} }
} }

View File

@ -1,8 +1,31 @@
--- ---
"node_reload_secure_settings test": "node_reload_secure_settings test wrong password":
- skip:
version: " - 7.99.99"
reason: "support for reloading password protected keystores was introduced in 7.7.0"
- do:
nodes.reload_secure_settings:
node_id: _local
body:
secure_settings_password: awrongpasswordhere
- set:
nodes._arbitrary_key_: node_id
- is_true: nodes
- is_true: cluster_name
- match: { nodes.$node_id.reload_exception.type: "security_exception" }
- match: { nodes.$node_id.reload_exception.reason: "Provided keystore password was incorrect" }
---
"node_reload_secure_settings test correct(empty) password":
- do: - do:
nodes.reload_secure_settings: {} nodes.reload_secure_settings: {}
- set:
nodes._arbitrary_key_: node_id
- is_true: nodes - is_true: nodes
- is_true: cluster_name - is_true: cluster_name
- is_false: nodes.$node_id.reload_exception

View File

@ -75,7 +75,7 @@ public final class RestReloadSecureSettingsAction extends BaseRestHandler {
.setNodesIds(nodesIds); .setNodesIds(nodesIds);
request.withContentOrSourceParamParserOrNull(parser -> { request.withContentOrSourceParamParserOrNull(parser -> {
if (parser != null) { if (parser != null) {
final NodesReloadSecureSettingsRequest nodesRequest = nodesRequestBuilder.request(); final NodesReloadSecureSettingsRequest nodesRequest = PARSER.parse(parser, null);
nodesRequestBuilder.setSecureStorePassword(nodesRequest.getSecureSettingsPassword()); nodesRequestBuilder.setSecureStorePassword(nodesRequest.getSecureSettingsPassword());
} }
}); });

View File

@ -0,0 +1,52 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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.
*/
/*
* Tests that need to run against an Elasticsearch cluster that
* is using a password protected keystore in its nodes.
*/
apply plugin: 'elasticsearch.testclusters'
apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.rest-test'
dependencies {
testCompile project(path: xpackModule('core'), configuration: 'default')
}
testClusters.integTest {
testDistribution = 'DEFAULT'
numberOfNodes = 2
keystorePassword 's3cr3t'
setting 'xpack.security.enabled', 'true'
setting 'xpack.security.authc.anonymous.roles', 'anonymous'
setting 'xpack.security.transport.ssl.enabled', 'true'
setting 'xpack.security.transport.ssl.certificate', 'transport.crt'
setting 'xpack.security.transport.ssl.key', 'transport.key'
setting 'xpack.security.transport.ssl.key_passphrase', 'transport-password'
setting 'xpack.security.transport.ssl.certificate_authorities', 'ca.crt'
extraConfigFile 'transport.key', file('src/test/resources/ssl/transport.key')
extraConfigFile 'transport.crt', file('src/test/resources/ssl/transport.crt')
extraConfigFile 'ca.crt', file('src/test/resources/ssl/ca.crt')
extraConfigFile 'roles.yml', file('src/test/resources/roles.yml')
user username: 'admin_user', password: 'admin-password'
user username:'test-user' ,password: 'test-password', role: 'user_role'
}

View File

@ -0,0 +1,97 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.password_protected_keystore;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.ObjectPath;
import org.elasticsearch.test.rest.ESRestTestCase;
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.nullValue;
import java.util.Map;
public class ReloadSecureSettingsWithPasswordProtectedKeystoreRestIT extends ESRestTestCase {
// From build.gradle
private final String KEYSTORE_PASSWORD = "s3cr3t";
private final int NUM_NODES = 2;
@SuppressWarnings("unchecked")
public void testReloadSecureSettingsWithCorrectPassword() throws Exception {
final Request request = new Request("POST", "_nodes/reload_secure_settings");
request.setJsonEntity("{\"secure_settings_password\":\"" + KEYSTORE_PASSWORD + "\"}");
final Response response = client().performRequest(request);
final Map<String, Object> map = entityAsMap(response);
assertThat(ObjectPath.eval("cluster_name", map), equalTo("integTest"));
assertThat(map.get("nodes"), instanceOf(Map.class));
final Map<String, Object> nodes = (Map<String, Object>) map.get("nodes");
assertThat(nodes.size(), equalTo(NUM_NODES));
for (Map.Entry<String, Object> entry : nodes.entrySet()) {
assertThat(entry.getValue(), instanceOf(Map.class));
final Map<String, Object> node = (Map<String, Object>) entry.getValue();
assertThat(node.get("reload_exception"), nullValue());
}
}
@SuppressWarnings("unchecked")
public void testReloadSecureSettingsWithInCorrectPassword() throws Exception {
final Request request = new Request("POST", "_nodes/reload_secure_settings");
request.setJsonEntity("{\"secure_settings_password\":\"" + KEYSTORE_PASSWORD + randomAlphaOfLength(7) + "\"}");
final Response response = client().performRequest(request);
final Map<String, Object> map = entityAsMap(response);
assertThat(ObjectPath.eval("cluster_name", map), equalTo("integTest"));
assertThat(map.get("nodes"), instanceOf(Map.class));
final Map<String, Object> nodes = (Map<String, Object>) map.get("nodes");
assertThat(nodes.size(), equalTo(NUM_NODES));
for (Map.Entry<String, Object> entry : nodes.entrySet()) {
assertThat(entry.getValue(), instanceOf(Map.class));
final Map<String, Object> node = (Map<String, Object>) entry.getValue();
assertThat(node.get("reload_exception"), instanceOf(Map.class));
assertThat(ObjectPath.eval("reload_exception.reason", node), equalTo("Provided keystore password was incorrect"));
assertThat(ObjectPath.eval("reload_exception.type", node), equalTo("security_exception"));
}
}
@SuppressWarnings("unchecked")
public void testReloadSecureSettingsWithEmptyPassword() throws Exception {
final Request request = new Request("POST", "_nodes/reload_secure_settings");
final Response response = client().performRequest(request);
final Map<String, Object> map = entityAsMap(response);
assertThat(ObjectPath.eval("cluster_name", map), equalTo("integTest"));
assertThat(map.get("nodes"), instanceOf(Map.class));
final Map<String, Object> nodes = (Map<String, Object>) map.get("nodes");
assertThat(nodes.size(), equalTo(NUM_NODES));
for (Map.Entry<String, Object> entry : nodes.entrySet()) {
assertThat(entry.getValue(), instanceOf(Map.class));
final Map<String, Object> node = (Map<String, Object>) entry.getValue();
assertThat(node.get("reload_exception"), instanceOf(Map.class));
assertThat(ObjectPath.eval("reload_exception.reason", node), equalTo("Provided keystore password was incorrect"));
assertThat(ObjectPath.eval("reload_exception.type", node), equalTo("security_exception"));
}
}
@Override
protected Settings restClientSettings() {
String token = basicAuthHeaderValue("test-user", new SecureString("test-password".toCharArray()));
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
@Override
protected Settings restAdminSettings() {
String token = basicAuthHeaderValue("admin_user", new SecureString("admin-password".toCharArray()));
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
}

View File

@ -0,0 +1,4 @@
# user needs to call cluster:admin/nodes/reload_secure_settings
user_role:
cluster: [ALL]
indices: []

View File

@ -0,0 +1,31 @@
= Keystore Details
This document details the steps used to create the certificate and keystore files in this directory.
== Instructions on generating certificates
The certificates in this directory have been generated using elasticsearch-certutil (8.0.0 SNAPSHOT)
[source,shell]
-----------------------------------------------------------------------------------------------------------
elasticsearch-certutil ca --pem --out=ca.zip --pass="ca-password" --days=3500
unzip ca.zip
mv ca/ca.* ./
rm ca.zip
rmdir ca
-----------------------------------------------------------------------------------------------------------
[source,shell]
-----------------------------------------------------------------------------------------------------------
elasticsearch-certutil cert --pem --name=transport --out=transport.zip --pass="transport-password" --days=3500 \
--ca-cert=ca.crt --ca-key=ca.key --ca-pass="ca-password" \
--dns=localhost --dns=localhost.localdomain --dns=localhost4 --dns=localhost4.localdomain4 --dns=localhost6 --dns=localhost6.localdomain6 \
--ip=127.0.0.1 --ip=0:0:0:0:0:0:0:1
unzip transport.zip
mv transport/transport.* ./
rm transport.zip
rmdir transport
-----------------------------------------------------------------------------------------------------------

View File

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,8209E02F62E3909502FECF5E5E9CF7A7
EdOFZ6/z/e4elfeAKs2B++/Px/IpiZdmiseZPjfwa6jgpY+8sehmze5+34vrxYJT
cMBH3QafmhdQZ4/Eo7DVFONrjJ3OmD5//ZiTIujTPwMsgGAdeq0yMC0cDkzg8SQ7
KvTh0PY0feC6bVsY+YjDprDfpqIWf89F8ikgat9cmucV9YO3RbYnxgxRIztbHLP3
GenAtdG+v7DzdefAdRQktBSNldkadsY6d/kVBknOHcA4pB/UtDpz77ZF40CNB95z
1Tr37nNnuRBUNHbKklXuozkvYLah66tFxA5v7Rf6F37d2QGBkgDphg/QMbJrrB+q
MsfiXeXqRaCzBN/ZuzTQAdQ/67XpQ+Ax89UOiT6SkKBKN1uNDk7Juzv5zHrq7aWS
aj1qtHDG2vMB+UM5A1MngD1LtXzs21Q0+9a2UT83x+VIP0hVq2uKmO8wAQ9gbBe9
wkBPca4gLYlbIMWzaAe4DV0rcmux+i7Ezk8oVYW1JcoGjoZ3f9KewIQynBUlXXuO
EzSl4R3yiF/birrK9Lo6c9hOcQKCW2qAX73BKq8PjKgWT3rnqzg97q9PPK/vaien
fwSrTXDgEoy1RCwsPsxjyRf0LGFYLUFRVqrbFPhhjg4aEiuzawcpvRxjorC5UX0L
dpImNssdALDd0BbiqAbChUryFSSxFhQ2yo6hfUXZevD236b09V0jUpnZeyQjeTTk
fhhAUUpnd1YzWuYneD2JZQKvGdgWgYRyEKParFeHLjp95rXNWPSOgoAM+w0fFEjq
zkYQMaDGSnUWbc6LVv2exyRIRTrLAWamKnne7z8VxzetqXXmuX0WJb2lFiYMUw4/
wf31RA8ZsVSgb9werSyPD9aRe/+YZM+kM3/3MC4jJGc6OJuDqEOhhB06L2Df2AWU
UQwZ7y+2yUC1kcFzc8+oT1TNgBHixouY+oqWkhbdCkbUFUe4FwXNXrMyrY9gZs1/
PEkhVxxYgpLwifkbfQRJPeJvXxh7NxeolXyISaVENdLkMMYUhdsKTa+GOQbO2yfa
4BhOwAqJvyDFfsRxLiDlbxjzvY5qnMl0e/q8wZ60onHJOFCTCfm2BNx7sW+Sk5Kx
zm0Rxsz4rIIxA5S6zbbdsHxjTC9XiUelKaq+W0XTg76USYneORQNN/Mk9sCXvTud
HUqmSf1wREA1PdEcoJ3tMoAOZWGY43/IrdoG3bTNT96AdToD+D+Or8M2VcOZorVf
c3IRNfxGv2/SwhxW/z4tSLSToSJlt4QKxU9Xzm4UundDy1cHmS1faN6+bBnI5+/F
OKwzPCCUJ6H02CAjx2P/P6YEjoLl8B+7h4whlOfT/+IQbzOcGMpPyGu4jSf1KffA
asAQeBvYTx0QPdv2E7e216RLOlp/ERMzkUvF1G7UYKF7Ao6cUpSH6nvGABPLKNXV
fqjpWq8O4R1UEUXi6dqF1HfAHllI+vMw7LzRJK/5zVrWlJPm4c/Rng5OkK7aAGee
J0eTSlCdNpyaZzjyk2ZAQ54kZVqAS90zS1zo6lg2v9yfAfz6eYlfl2OGfFVG40Jt
oYxEVcG9LeD3XOkPOnTblHdKMor8cQt+TEJPu9eM31ay1QSilixx2yfOOFTgJZOi
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,DAC0DDB93011ABD08161118074F353A7
hPjzr8y4t3omv6jItFSxF/UeirrdlMhFoxxsw+E5fl4hRjD2J6LuUpOl0XBuvrCO
2NN9Simlkfo57l2O8tZ3xwKU037x9qP2O3wo0FZ4OuRcLbXZtp5kIV30/wdo0kbp
GV+18PtGfReo75rszs/VAm9Hg1URqVw0La2r7DomYQB9FJY8N8mwSdSvF194kjGO
pBxiuzzECUwXEGuMRzmc1Cddbw7NsIdg43FRd1uoC4dqj9yBonYEYe5P8WgopL4N
obTi6PzH+kqDSCaJo7Fdr9CYo37f2YsSbtHmuEZP58J/aSB9nl5wdAmas3/dohrI
5GSM9zp+UocFuV6Uf+X9TTJMt97BlRgFdPODh88pTKGLVQyKeBPQbVjgwl9mttxO
i+c/dej/jHt0gwlt8cvZw0Ss50YdNnWtck91yYpXE7iz59CTY+QI24DEvsaP4bkR
QYdIhJHOYamGW0ttCSU8bw1h9RubIvSa+BoiuB+1TaCYU+azuaAYnFlyuR31z4rD
yniPMnb0+5uOkU/srwb4MxVVw/0iYkKAGTEwdLPKhyheuDU9ixkNOQ/k12zV0R7d
gzMFQOlrB4v8Y4LrsNPnAz/uCTvKgBrOS8p076qeGkSX+JIZVNHYyzLnSy7p6hjO
eD3tDx/SA1HaiLzD1VqujnYb6wshYjQGkSPSY3COq8dQgpCqMAlkOycUQO1SbuNt
HZFv9X0w2z5HjPJXtKLLXMLeluNNRQD+IVhvbZjIM1cAUQNqL3OQPGa7W5RYoFYK
rDffzQAzukD5dt6jH+uu3cwnEeJiW8hxZ0+DHJR1X5EJWpN544yTl8jgSPT8MPAU
kxq7OyE0F/JY+UWP1hPILimDrf3Ov8KRtTDGsSvV3IcX+92QKMcvnK21QBZqZjSs
zcmjp2jN1MLwieJyZ3un0MUT9kOyG/5vGoAJe9O/KDtv6rrhKQN5JHi5yKw0Uwi9
CwrwwkxbRLSBbWugZGXyBHkR/RGIuEEysLKRFej2q4WBZrPOzZlgyvgBbd6/4Eg5
twngo6JTmYALwVJNW/drok1H0JelanZ6jM/JjNRFWMZnS5o+4cwQURB5O7TIKHdV
7zEkaw82Ng0Nq8dPrU8N9G3LTmIC1E4t++L+D18C2lV0ZDd0Svh3NIA65FXSRvX9
2g9GQGfGGbgydmjv9j5lx6VdhuTgYKRL4b5bS7VnH+F9m864g/MtSQpXQPR5B54g
YHFGiKCAzruZt1MmJ5m8Jvpg84i2lIZkGImwAstV7xVkmQoC3i77awmcQP6s7rJd
Lo7RKEysVPDbzdnZnWGK0PWJwtgsqrAvVcK7ghygi+vSQkDF0L7qunchOKa00oZR
LZa08b5BWuXeqw4lXZDQDT7hk3NUyW3H7Z1uxUlt1kvcGb6zrInW6Bin0hqsODvj
0drMOZp/5NTDSwcEzkW+LgjfKZw8Szmhlt3v+luNFr3KzbnFtEvewD1OVikNGzm9
sfZ899zNkWfvNJaXL3bvzbTn9d8T15YKCwO9RqPpYKDqXBaC4+OjbNsy4AW/JHPr
H/i3D3rhMXR/CALhp4+Knq4o3vMA+3TsUeZ3lOTogobVloWfixIIiRXfaqT4LmEC
-----END RSA PRIVATE KEY-----