Skip to content

Commit

Permalink
[8.12] Validate settings before reloading JWT shared secret (elastic#…
Browse files Browse the repository at this point in the history
…105070) (elastic#105142)

* Backport of: Validate settings before reloading JWT shared secret (elastic#105070)

This PR adds missing validation before reloading JWT shared secret settings.
The shared secret setting must always be configured when the client
authentication type is `shared_secret` and omitted when it's `none`.

(cherry picked from commit 8b7c777)

Note: This backport also includes the required test changes from elastic#103133 and elastic#103429 PRs.
  • Loading branch information
slobodanadamovic authored Feb 6, 2024
1 parent 9386542 commit 8af123a
Show file tree
Hide file tree
Showing 14 changed files with 364 additions and 10 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/105070.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 105070
summary: Validate settings before reloading JWT shared secret
area: Authentication
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,21 @@ private void copyExtraConfigFiles() {
});
}

public void updateStoredSecureSettings() {
if (usesSecureSecretsFile) {
throw new UnsupportedOperationException("updating stored secure settings is not supported in serverless test clusters");
}
final Path keystoreFile = workingDir.resolve("config").resolve("elasticsearch.keystore");
try {
Files.deleteIfExists(keystoreFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
createKeystore();
addKeystoreSettings();
addKeystoreFiles();
}

private void createKeystore() {
if (spec.getKeystorePassword() == null || spec.getKeystorePassword().isEmpty()) {
runToolScript("elasticsearch-keystore", null, "-v", "create");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ public InputStream getNodeLog(int index, LogType logType) {
return nodes.get(index).getLog(logType);
}

@Override
public void updateStoredSecureSettings() {
execute(() -> nodes.parallelStream().forEach(Node::updateStoredSecureSettings));
}

protected void waitUntilReady() {
writeUnicastHostsFile();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ public InputStream getNodeLog(int index, LogType logType) {
return handle.getNodeLog(index, logType);
}

@Override
public void updateStoredSecureSettings() {
checkHandle();
handle.updateStoredSecureSettings();
}

protected H getHandle() {
return handle;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.elasticsearch.test.cluster.ClusterHandle;
import org.elasticsearch.test.cluster.LogType;
import org.elasticsearch.test.cluster.MutableSettingsProvider;
import org.elasticsearch.test.cluster.util.Version;

import java.io.InputStream;
Expand Down Expand Up @@ -93,4 +94,13 @@ public interface LocalClusterHandle extends ClusterHandle {
* Returns an {@link InputStream} for the given node log.
*/
InputStream getNodeLog(int index, LogType logType);

/**
* Writes secure settings to the relevant secure config file on each node. Use this method if you are dynamically updating secure
* settings via a {@link MutableSettingsProvider} and need the update to be written to file, without a cluster restart.
*
* @throws UnsupportedOperationException if secure settings are stored in a secrets file, i.e., in serverless. Only keystore-based
* storage is currently supported
*/
void updateStoredSecureSettings();
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,23 @@

import org.apache.http.HttpHost;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.PathUtils;
import org.elasticsearch.core.Strings;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.TestSecurityClient;
import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.test.cluster.MutableSettingsProvider;
import org.elasticsearch.test.cluster.local.LocalClusterSpec;
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
import org.elasticsearch.test.cluster.util.resource.Resource;
Expand Down Expand Up @@ -66,6 +70,7 @@
import static org.elasticsearch.test.TestMatchers.hasStatusCode;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasKey;
Expand All @@ -81,20 +86,32 @@ public class JwtRestIT extends ESRestTestCase {
]}""".replaceAll("\\s", "");
public static final String HMAC_PASSPHRASE = "test-HMAC/secret passphrase-value";
private static final String VALID_SHARED_SECRET = "test-secret";
private static final MutableSettingsProvider keystoreSettings = new MutableSettingsProvider() {
{
put("xpack.security.authc.realms.jwt.jwt2.client_authentication.shared_secret", VALID_SHARED_SECRET);
}
};
private static final String KEYSTORE_PASSWORD = "keystore-password";

@ClassRule
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
.nodes(2)
.distribution(DistributionType.DEFAULT)
.keystorePassword(KEYSTORE_PASSWORD)
.configFile("http.key", Resource.fromClasspath("ssl/http.key"))
.configFile("http.crt", Resource.fromClasspath("ssl/http.crt"))
.configFile("ca.crt", Resource.fromClasspath("ssl/ca.crt"))
.configFile("ca-transport.crt", Resource.fromClasspath("ssl/ca-transport.crt"))
.configFile("transport.key", Resource.fromClasspath("ssl/transport.key"))
.configFile("transport.crt", Resource.fromClasspath("ssl/transport.crt"))
.configFile("rsa.jwkset", Resource.fromClasspath("jwk/rsa-public-jwkset.json"))
.setting("xpack.ml.enabled", "false")
.setting("xpack.license.self_generated.type", "trial")
.setting("xpack.security.enabled", "true")
.setting("xpack.security.http.ssl.enabled", "true")
.setting("xpack.security.transport.ssl.enabled", "false")
.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.certificate_authorities", "ca-transport.crt")
.setting("xpack.security.authc.token.enabled", "true")
.setting("xpack.security.authc.api_key.enabled", "true")

Expand All @@ -105,10 +122,11 @@ public class JwtRestIT extends ESRestTestCase {
.setting("xpack.security.http.ssl.certificate_authorities", "ca.crt")
.setting("xpack.security.http.ssl.client_authentication", "optional")
.settings(JwtRestIT::realmSettings)
.keystore("xpack.security.authc.realms.jwt.jwt2.client_authentication.shared_secret", VALID_SHARED_SECRET)
.keystore("xpack.security.authc.realms.jwt.jwt2.hmac_key", HMAC_PASSPHRASE)
.keystore("xpack.security.authc.realms.jwt.jwt3.hmac_jwkset", HMAC_JWKSET)
.keystore("xpack.security.transport.ssl.secure_key_passphrase", "transport-password")
.keystore("xpack.security.authc.realms.jwt.jwt3.client_authentication.shared_secret", VALID_SHARED_SECRET)
.keystore(keystoreSettings)
.user("admin_user", "admin-password")
.user("test_file_user", "test-password", "viewer", false)
.build();
Expand Down Expand Up @@ -170,6 +188,7 @@ private static Map<String, String> realmSettings(LocalClusterSpec.LocalNodeSpec
settings.put("xpack.security.authc.realms.jwt.jwt2.required_claims.token_use", "access");
settings.put("xpack.security.authc.realms.jwt.jwt2.authorization_realms", "lookup_native");
settings.put("xpack.security.authc.realms.jwt.jwt2.client_authentication.type", "shared_secret");
settings.put("xpack.security.authc.realms.jwt.jwt2.client_authentication.rotation_grace_period", "0s");

// Place PKI realm after JWT realm to verify realm chain fall-through
settings.put("xpack.security.authc.realms.pki.pki_realm.order", "4");
Expand Down Expand Up @@ -499,6 +518,71 @@ public void testAuthenticationFailureIfDelegatedAuthorizationFails() throws Exce
}
}

public void testReloadClientSecret() throws Exception {
final String principal = SERVICE_SUBJECT.get();
final String username = getUsernameFromPrincipal(principal);
final List<String> roles = randomRoles();
createUser(username, roles, Map.of());

try {
getSecurityClient(buildAndSignJwtForRealm2(principal), Optional.of(VALID_SHARED_SECRET)).authenticate();

// secret not updated yet, so authentication fails
final String newValidSharedSecret = "new-valid-secret";
assertThat(
expectThrows(
ResponseException.class,
() -> getSecurityClient(buildAndSignJwtForRealm2(principal), Optional.of(newValidSharedSecret)).authenticate()
).getResponse(),
hasStatusCode(RestStatus.UNAUTHORIZED)
);

writeSettingToKeystoreThenReload(
"xpack.security.authc.realms.jwt.jwt2.client_authentication.shared_secret",
newValidSharedSecret
);

// secret updated, so authentication succeeds
getSecurityClient(buildAndSignJwtForRealm2(principal), Optional.of(newValidSharedSecret)).authenticate();

// removing setting should not work since it can
// lead to inconsistency in realm's configuration
// and eventual authentication failures
Response reloadResponse = writeSettingToKeystoreThenReload(
"xpack.security.authc.realms.jwt.jwt2.client_authentication.shared_secret",
null
);
assertThat(
responseAsMap(reloadResponse).toString(),
containsString(
"Missing setting for [xpack.security.authc.realms.jwt.jwt2.client_authentication.shared_secret]. "
+ "It is required when setting [xpack.security.authc.realms.jwt.jwt2.client_authentication.type] is [shared_secret]"
)
);
getSecurityClient(buildAndSignJwtForRealm2(principal), Optional.of(newValidSharedSecret)).authenticate();

} finally {
// Restore setting for other tests
writeSettingToKeystoreThenReload(
"xpack.security.authc.realms.jwt.jwt2.client_authentication.shared_secret",
VALID_SHARED_SECRET
);
deleteUser(username);
}
}

private Response writeSettingToKeystoreThenReload(String setting, @Nullable String value) throws IOException {
if (value == null) {
keystoreSettings.remove(setting);
} else {
keystoreSettings.put(setting, value);
}
cluster.updateStoredSecureSettings();
final var reloadRequest = new Request("POST", "/_nodes/reload_secure_settings");
reloadRequest.setJsonEntity("{\"secure_settings_password\":\"" + KEYSTORE_PASSWORD + "\"}");
return assertOK(adminClient().performRequest(reloadRequest));
}

public void testFailureOnInvalidClientAuthentication() throws Exception {
final String principal = SERVICE_SUBJECT.get();
final String username = getUsernameFromPrincipal(principal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ rm http.zip
rmdir http
-----------------------------------------------------------------------------------------------------------

[source,shell]
-----------------------------------------------------------------------------------------------------------
elasticsearch-certutil cert --pem --name=transport --out=transport.zip --pass="transport-password" --days=3500 \
--ca-cert=ca-transport.crt --ca-key=ca-transport.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
-----------------------------------------------------------------------------------------------------------

[source,shell]
-----------------------------------------------------------------------------------------------------------
elasticsearch-certutil cert --pem --name=pki --out=${PWD}/pki.zip --pass="pki-password" --days=3500 \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDSTCCAjGgAwIBAgIUGuBmPtwyEv7WZ1H0Yy5vyEEYVR8wDQYJKoZIhvcNAQEL
BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
cmF0ZWQgQ0EwHhcNMjAwNDA3MTEzMDA1WhcNMjkxMTA2MTEzMDA1WjA0MTIwMAYD
VQQDEylFbGFzdGljIENlcnRpZmljYXRlIFRvb2wgQXV0b2dlbmVyYXRlZCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALeTNx0a6X+Fhf6IQj4ggN9U
1HGIzJKEHGIpATDgbfdIv88e0O0I6HN7pmLf5LuUPDGc2oLGnxqATgnFek5eJ4QW
sKgflGB4C0EgQH4JAooIG0EI6aj3IcdzBwH8bdymAdsGj0Zcvm6wjhLixgiN3yIM
8KJAtJrSCITI88gfXhXyU0XCSzgruFkdvHjFBCWpCaK3hnjoiO65186PcGbrZHB8
Izs2soa6H1AHVDMhmJjlwJWYtibjok+sgrjkDWG7cBh6Al7yXGUBOs9SgMXUpI3Y
0r/dDdeISdI5VzwKZpX6qYcNJI+jtgZUD0alMKBxjq3+v8GlDE/QVNyDwp/7SA8C
AwEAAaNTMFEwHQYDVR0OBBYEFMdSVLWtAhqfDXRQj+5o80nK1XaQMB8GA1UdIwQY
MBaAFMdSVLWtAhqfDXRQj+5o80nK1XaQMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggEBAEiaX+JtgDb6PuPqO9NtxuPEsg6Ghj0dVYLOYE9cmKS9xeyy
sIDJbPl8vy7gomVBDYe+3qM2UElKFdApKGjOctjNlH8npkQxg72sarBUzOfuFPx5
Z6u5TuKMfqjx2dwpTr3Yp/iUFs9e9zcDMnQYt5B6SCjKpKKodKKh+CE6wOg4nbSF
43OYO9HFHfwIlEqvof7j0r5gnPf5fYSYybYFAqy4oAfpESPq9lJuEvA46TrGpmP6
IpMYkJJ6O+98A7RHo5kstZJdnG5paAKobdPEYxbIZvRyMJ8IxW8kSAaTKsK7W34k
IYciDd/YY3R+nhnh8F5DjVcyc79Zkv9Cjig/OxQ=
-----END CERTIFICATE-----
Original file line number Diff line number Diff line change
@@ -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-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIVAIdpYPATbRn96E+eVTG/s0byNh/FMA0GCSqGSIb3DQEB
CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu
ZXJhdGVkIENBMB4XDTIwMDQwNzExMzA1OFoXDTI5MTEwNjExMzA1OFowFDESMBAG
A1UEAxMJdHJhbnNwb3J0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
pvfY9x3F8xR2+noX0PWxsc6XrKCxTQcbSHkkEr2Qr6BqGVXwEj/d6ocqdgcC7IZy
0HEwewBbO69Pg8INQ/21stcwZzW+4ZnKYsBmaZx1yCxYcplndFm9t5Fj/jTBsoJ3
PemvJwMrewuVSwsE4WHVkXz4KVETfUd8DZiWoVnlRgaXfvvudib1DNxtuGEra+Zh
d3JcC1Wsn51qjpHsj/6s/usT6hmlm4Bu5tjAMxXFVX6J0skfRSVhLmNWgr86WBKB
9/qTJU34FBQGh2Ok/knkiO5rae+UCPpEpCNCCV3rFcMdxP613WfemRRqhUL0V6g3
n4ExJa0853SsfvPEyHOADQIDAQABo4HgMIHdMB0GA1UdDgQWBBSraIvkNPX2TQQg
h8Ee3mWCALYr/zAfBgNVHSMEGDAWgBTHUlS1rQIanw10UI/uaPNJytV2kDCBjwYD
VR0RBIGHMIGEgglsb2NhbGhvc3SCF2xvY2FsaG9zdDYubG9jYWxkb21haW42hwR/
AAABhxAAAAAAAAAAAAAAAAAAAAABggpsb2NhbGhvc3Q0ggpsb2NhbGhvc3Q2ghVs
b2NhbGhvc3QubG9jYWxkb21haW6CF2xvY2FsaG9zdDQubG9jYWxkb21haW40MAkG
A1UdEwQCMAAwDQYJKoZIhvcNAQELBQADggEBABRKISeKdMRRQAvZBan2ONUFUD3P
7Aq8JP+GttC9Y+1uI0CTIZsnih+tuZ9l2Gq9nyfBVMcDSutlnBoQZLg5lCYQPaK7
SuFhPRHGauEGYw1TjhrMEPokxzoT/X0/Sz5Ter6/qWzPKQs9EuaVJ27XfZd+kySn
S+cXd95woi+S8JQzQbcpA0rXCnWJ3l2frwG/3Hg8f82x2c6vgOzTG0Hklp0sFkUt
UqaBHGXPLiitaB01jUX60HZbxt5HIEseLctUmQlDtAEWwA3X6cRUEjulwRx8s52T
1FT2ORbVJ7ybKARGBSs932Fv2rWGmg8pOBA4ulJTJNvT0T+ob/H/i40Qd04=
-----END CERTIFICATE-----
Original file line number Diff line number Diff line change
@@ -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-----
Loading

0 comments on commit 8af123a

Please sign in to comment.