From 77cd975164d60c089b6a42b2e9bd128c0e037025 Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Sat, 11 Feb 2017 15:03:01 +0000 Subject: [PATCH] Add framework for managing letsencrypt certificates --- .kitchen.yml | 9 ++ cookbooks/letsencrypt/.foodcritic | 4 + cookbooks/letsencrypt/README.md | 4 + cookbooks/letsencrypt/files/default/bin/renew | 8 + .../letsencrypt/files/default/bin/renew-hook | 5 + .../letsencrypt/files/default/bin/upload | 20 +++ cookbooks/letsencrypt/files/default/knife.rb | 11 ++ cookbooks/letsencrypt/metadata.rb | 8 + cookbooks/letsencrypt/recipes/default.rb | 149 ++++++++++++++++++ .../letsencrypt/templates/default/apache.erb | 31 ++++ .../letsencrypt/templates/default/cron.erb | 1 + .../letsencrypt/templates/default/request.erb | 20 +++ cookbooks/ssl/.foodcritic | 1 + cookbooks/ssl/resources/certificate.rb | 96 +++++++++++ cookbooks/ssl/templates/default/ssl.cnf.erb | 20 +++ roles/ironbelly.rb | 1 + roles/letsencrypt.rb | 16 ++ test/data_bags/accounts/letsencrypt.json | 6 + test/data_bags/chef/keys.json | 61 +++++++ 19 files changed, 471 insertions(+) create mode 100644 cookbooks/letsencrypt/.foodcritic create mode 100644 cookbooks/letsencrypt/README.md create mode 100755 cookbooks/letsencrypt/files/default/bin/renew create mode 100755 cookbooks/letsencrypt/files/default/bin/renew-hook create mode 100755 cookbooks/letsencrypt/files/default/bin/upload create mode 100644 cookbooks/letsencrypt/files/default/knife.rb create mode 100644 cookbooks/letsencrypt/metadata.rb create mode 100644 cookbooks/letsencrypt/recipes/default.rb create mode 100644 cookbooks/letsencrypt/templates/default/apache.erb create mode 100644 cookbooks/letsencrypt/templates/default/cron.erb create mode 100644 cookbooks/letsencrypt/templates/default/request.erb create mode 100644 cookbooks/ssl/resources/certificate.rb create mode 100644 cookbooks/ssl/templates/default/ssl.cnf.erb create mode 100644 roles/letsencrypt.rb create mode 100644 test/data_bags/accounts/letsencrypt.json create mode 100644 test/data_bags/chef/keys.json diff --git a/.kitchen.yml b/.kitchen.yml index c5f8a6988..c49fa230f 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -36,6 +36,15 @@ suites: run_list: - recipe[accounts::default] - role[forum] + - name: letsencrypt + run_list: + - recipe[accounts::default] + - recipe[apt::default] + - role[letsencrypt] + attributes: + apt: + sources: + - openstreetmap - name: munin run_list: - recipe[munin::default] diff --git a/cookbooks/letsencrypt/.foodcritic b/cookbooks/letsencrypt/.foodcritic new file mode 100644 index 000000000..d8973b2fb --- /dev/null +++ b/cookbooks/letsencrypt/.foodcritic @@ -0,0 +1,4 @@ +~FC001 +~FC003 +~FC064 +~FC065 diff --git a/cookbooks/letsencrypt/README.md b/cookbooks/letsencrypt/README.md new file mode 100644 index 000000000..ad5f5e766 --- /dev/null +++ b/cookbooks/letsencrypt/README.md @@ -0,0 +1,4 @@ +# Letsencrypt Cookbook + +This cookbook provides lightweight resources to obtain and +manage letsencrypt certficates. diff --git a/cookbooks/letsencrypt/files/default/bin/renew b/cookbooks/letsencrypt/files/default/bin/renew new file mode 100755 index 000000000..d5b7232bf --- /dev/null +++ b/cookbooks/letsencrypt/files/default/bin/renew @@ -0,0 +1,8 @@ +#!/bin/sh + +/usr/bin/certbot renew \ + --quiet \ + --config-dir /srv/acme.openstreetmap.org/config \ + --work-dir /srv/acme.openstreetmap.org/work \ + --logs-dir /srv/acme.openstreetmap.org/logs \ + --renew-hook /srv/acme.openstreetmap.org/renew-hook diff --git a/cookbooks/letsencrypt/files/default/bin/renew-hook b/cookbooks/letsencrypt/files/default/bin/renew-hook new file mode 100755 index 000000000..26bf656b8 --- /dev/null +++ b/cookbooks/letsencrypt/files/default/bin/renew-hook @@ -0,0 +1,5 @@ +#!/bin/sh + +domains=($RENEWED_DOMAINS) + +exec /srv/acme.openstreetmap.org/bin/upload "${domains[0]}" "$RENEWED_LINEAGE" diff --git a/cookbooks/letsencrypt/files/default/bin/upload b/cookbooks/letsencrypt/files/default/bin/upload new file mode 100755 index 000000000..56da6294c --- /dev/null +++ b/cookbooks/letsencrypt/files/default/bin/upload @@ -0,0 +1,20 @@ +#!/usr/bin/ruby + +require "json" +require "tempfile" + +domain = ARGV.shift +directory = ARGV.shift + +bag = { + :id => domain, + :key => File.read(File.join(directory, "privkey.pem")), + :certificate => File.read(File.join(directory, "fullchain.pem")) +} + +file = Tempfile.new(["letsencrypt", ".json"]) + +file.puts JSON.generate(bag) +file.close + +system("/usr/bin/knife", "data", "bag", "from", "file", "letsencrypt", file.path) diff --git a/cookbooks/letsencrypt/files/default/knife.rb b/cookbooks/letsencrypt/files/default/knife.rb new file mode 100644 index 000000000..24cb9ce9e --- /dev/null +++ b/cookbooks/letsencrypt/files/default/knife.rb @@ -0,0 +1,11 @@ +node_name "letsencrypt" +client_key "client.pem" +validation_client_name "chef-validator" +validation_key "/etc/chef/validation.pem" +chef_server_url "https://chef.openstreetmap.org/organizations/openstreetmap" +cache_type "BasicFile" +cache_options :path => ".chef/checksums" +cookbook_path ["cookbooks"] +cookbook_copyright "OpenStreetMap Administrators" +cookbook_email "admins@openstreetmap.org" +cookbook_license "apachev2" diff --git a/cookbooks/letsencrypt/metadata.rb b/cookbooks/letsencrypt/metadata.rb new file mode 100644 index 000000000..69f1d32db --- /dev/null +++ b/cookbooks/letsencrypt/metadata.rb @@ -0,0 +1,8 @@ +name "letsencrypt" +maintainer "OpenStreetMap Administrators" +maintainer_email "admins@openstreetmap.org" +license "Apache 2.0" +description "Support for letsencrypt certificates" +long_description IO.read(File.join(File.dirname(__FILE__), "README.md")) +version "1.0.0" +depends "apache" diff --git a/cookbooks/letsencrypt/recipes/default.rb b/cookbooks/letsencrypt/recipes/default.rb new file mode 100644 index 000000000..f08fdf3ac --- /dev/null +++ b/cookbooks/letsencrypt/recipes/default.rb @@ -0,0 +1,149 @@ +# +# Cookbook Name:: letsencrypt +# Recipe:: default +# +# Copyright 2017, OpenStreetMap Foundation +# +# Licensed 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. +# + +include_recipe "apache::ssl" + +keys = data_bag_item("chef", "keys") + +package "certbot" +package "ruby" + +directory "/etc/letsencrypt" do + owner "letsencrypt" + group "letsencrypt" + mode 0o755 +end + +directory "/var/lib/letsencrypt" do + owner "letsencrypt" + group "letsencrypt" + mode 0o755 +end + +directory "/var/log/letsencrypt" do + owner "letsencrypt" + group "letsencrypt" + mode 0o700 +end + +directory "/srv/acme.openstreetmap.org" do + owner "letsencrypt" + group "letsencrypt" + mode 0o755 +end + +directory "/srv/acme.openstreetmap.org/html" do + owner "letsencrypt" + group "letsencrypt" + mode 0o755 +end + +ssl_certificate "acme.openstreetmap.org" do + domains ["acme.openstreetmap.org", "acme.osm.org"] + notifies :reload, "service[apache2]" +end + +apache_site "acme.openstreetmap.org" do + template "apache.erb" + directory "/srv/acme.openstreetmap.org" +end + +directory "/srv/acme.openstreetmap.org/config" do + owner "letsencrypt" + group "letsencrypt" + mode 0o755 +end + +directory "/srv/acme.openstreetmap.org/work" do + owner "letsencrypt" + group "letsencrypt" + mode 0o755 +end + +directory "/srv/acme.openstreetmap.org/logs" do + owner "letsencrypt" + group "letsencrypt" + mode 0o700 +end + +directory "/srv/acme.openstreetmap.org/.chef" do + owner "letsencrypt" + group "letsencrypt" + mode 0o2775 +end + +file "/srv/acme.openstreetmap.org/.chef/client.pem" do + content keys["letsencrypt"].join("\n") + owner "letsencrypt" + group "letsencrypt" + mode 0o660 +end + +cookbook_file "/srv/acme.openstreetmap.org/.chef/knife.rb" do + source "knife.rb" + owner "letsencrypt" + group "letsencrypt" + mode 0o660 +end + +remote_directory "/srv/acme.openstreetmap.org/bin" do + source "bin" + owner "root" + group "root" + mode 0o755 + files_owner "root" + files_group "root" + files_mode 0o755 +end + +directory "/srv/acme.openstreetmap.org/requests" do + owner "root" + group "root" + mode 0o755 +end + +certificates = search(:node, "letsencrypt:certificates").each_with_object({}) do |n, c| + c.merge!(n[:letsencrypt][:certificates]) +end + +certificates.each do |name, details| + template "/srv/acme.openstreetmap.org/requests/#{name}" do + source "request.erb" + owner "root" + group "letsencrypt" + mode 0o754 + variables details + end + + execute "/srv/acme.openstreetmap.org/requests/#{name}" do + action :nothing + command "/srv/acme.openstreetmap.org/requests/#{name}" + cwd "/srv/acme.openstreetmap.org" + user "letsencrypt" + group "letsencrypt" + subscribes :run, "template[/srv/acme.openstreetmap.org/requests/#{name}]" + end +end + +template "/etc/cron.d/letsencrypt" do + source "cron.erb" + owner "root" + group "root" + mode 0o644 +end diff --git a/cookbooks/letsencrypt/templates/default/apache.erb b/cookbooks/letsencrypt/templates/default/apache.erb new file mode 100644 index 000000000..10d4c526e --- /dev/null +++ b/cookbooks/letsencrypt/templates/default/apache.erb @@ -0,0 +1,31 @@ +# DO NOT EDIT - This file is being maintained by Chef + + + ServerName acme.openstreetmap.org + ServerAlias acme.osm.org + ServerAdmin webmaster@openstreetmap.org + + CustomLog /var/log/apache2/acme.openstreetmap.org-access.log combined + ErrorLog /var/log/apache2/acme.openstreetmap.org-error.log + + DocumentRoot /srv/acme.openstreetmap.org/html + + + + ServerName acme.openstreetmap.org + ServerAlias acme.osm.org + ServerAdmin webmaster@openstreetmap.org + + CustomLog /var/log/apache2/acme.openstreetmap.org-access.log combined + ErrorLog /var/log/apache2/acme.openstreetmap.org¬-error.log + + SSLEngine on + SSLCertificateFile /etc/ssl/certs/acme.openstreetmap.org.pem + SSLCertificateKeyFile /etc/ssl/private/acme.openstreetmap.org.key + + DocumentRoot /srv/acme.openstreetmap.org/html + + + + Require all granted + diff --git a/cookbooks/letsencrypt/templates/default/cron.erb b/cookbooks/letsencrypt/templates/default/cron.erb new file mode 100644 index 000000000..88fd43fad --- /dev/null +++ b/cookbooks/letsencrypt/templates/default/cron.erb @@ -0,0 +1 @@ +0 */12 * * * letsencrypt /srv/acme.openstreetmap.org/renew diff --git a/cookbooks/letsencrypt/templates/default/request.erb b/cookbooks/letsencrypt/templates/default/request.erb new file mode 100644 index 000000000..6d4c2b1c2 --- /dev/null +++ b/cookbooks/letsencrypt/templates/default/request.erb @@ -0,0 +1,20 @@ +#!/bin/sh + +# DO NOT EDIT - This file is being maintained by Chef + +/usr/bin/certbot certonly \ + --non-interactive \ + --config-dir /srv/acme.openstreetmap.org/config \ + --work-dir /srv/acme.openstreetmap.org/work \ + --logs-dir /srv/acme.openstreetmap.org/logs \ + --email operations@osmfoundation.org \ + --agree-tos \ +<% @domains.each do |domain| -%> + --domain <%= domain %> \ +<% end -%> + --webroot \ + --webroot-path /srv/acme.openstreetmap.org/html + +/srv/acme.openstreetmap.org/bin/upload \ + <%= @domains.first %> \ + /srv/acme.openstreetmap.org/config/live/<%= @domains.first %> diff --git a/cookbooks/ssl/.foodcritic b/cookbooks/ssl/.foodcritic index a085263c8..d8973b2fb 100644 --- a/cookbooks/ssl/.foodcritic +++ b/cookbooks/ssl/.foodcritic @@ -1,3 +1,4 @@ ~FC001 +~FC003 ~FC064 ~FC065 diff --git a/cookbooks/ssl/resources/certificate.rb b/cookbooks/ssl/resources/certificate.rb new file mode 100644 index 000000000..92dbcc323 --- /dev/null +++ b/cookbooks/ssl/resources/certificate.rb @@ -0,0 +1,96 @@ +# +# Cookbook Name:: ssl +# Resource:: ssl_certificate +# +# Copyright 2017, OpenStreetMap Foundation +# +# Licensed 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. +# + +default_action :create + +property :name, String +property :domains, Array, :required => true +property :fallback_certificate, String + +action :create do + node.default[:letsencrypt][:certificates][name] = { + :domains => domains + } + + if letsencrypt + certificate = letsencrypt["certificate"] + key = letsencrypt["key"] + end + + if certificate + file "/etc/ssl/certs/#{name}.pem" do + owner "root" + group "root" + mode 0o444 + content certificate + backup false + end + + file "/etc/ssl/private/#{name}.key" do + owner "root" + group "ssl-cert" + mode 0o440 + content key + backup false + end + elsif fallback_certificate + link "/etc/ssl/certs/#{name}.pem" do + to "#{fallback_certificate}.pem" + end + + link "/etc/ssl/private/#{name}.key" do + to "#{fallback_certificate}.key" + end + else + template "/tmp/#{name}.ssl.cnf" do + cookbook "ssl" + source "ssl.cnf.erb" + owner "root" + group "root" + mode 0o644 + variables :domains => new_resource.domains + not_if do + ::File.exist?("/etc/ssl/certs/#{new_resource.name}.pem") && ::File.exist?("/etc/ssl/private/#{new_resource.name}.key") + end + end + + execute "/etc/ssl/certs/#{name}.pem" do + command "openssl req -x509 -newkey rsa:2048 -keyout /etc/ssl/private/#{new_resource.name}.key -out /etc/ssl/certs/#{new_resource.name}.pem -days 365 -nodes -config /tmp/#{new_resource.name}.ssl.cnf" + user "root" + group "ssl-cert" + not_if do + ::File.exist?("/etc/ssl/certs/#{new_resource.name}.pem") && ::File.exist?("/etc/ssl/private/#{new_resource.name}.key") + end + end + end +end + +action :delete do + file "/etc/ssl/certs/#{name}.pem" do + action :delete + end + + file "/etc/ssl/private/#{name}.key" do + action :delete + end +end + +def letsencrypt + @letsencrypt ||= search(:letsencrypt, "id:#{name}").first +end diff --git a/cookbooks/ssl/templates/default/ssl.cnf.erb b/cookbooks/ssl/templates/default/ssl.cnf.erb new file mode 100644 index 000000000..64c3c085d --- /dev/null +++ b/cookbooks/ssl/templates/default/ssl.cnf.erb @@ -0,0 +1,20 @@ +[req] +prompt = no +distinguished_name = req_dn +x509_extensions = v3_req + +[req_dn] +organizationName = OpenStreetMap +commonName = <%= @domains.first %> +emailAddress = operations@osmfoundation.org + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth, clientAuth +subjectAltName = @alt_names + +[alt_names] +<% @domains.each_with_index do |d, i| -%> +DNS.<%= i + 1 %> = <%= d %> +<% end -%> diff --git a/roles/ironbelly.rb b/roles/ironbelly.rb index 3edf96241..d5bb0ee33 100644 --- a/roles/ironbelly.rb +++ b/roles/ironbelly.rb @@ -135,6 +135,7 @@ run_list( "role[planet]", "role[planetdump]", "role[logstash]", + "role[letsencrypt]", "recipe[rsyncd]", "recipe[openvpn]", "recipe[git::server]", diff --git a/roles/letsencrypt.rb b/roles/letsencrypt.rb new file mode 100644 index 000000000..c0b389da9 --- /dev/null +++ b/roles/letsencrypt.rb @@ -0,0 +1,16 @@ +name "letsencrypt" +description "Role applied to all letsencrypt servers" + +default_attributes( + :accounts => { + :users => { + :letsencrypt => { + :status => :role + } + } + } +) + +run_list( + "recipe[letsencrypt]" +) diff --git a/test/data_bags/accounts/letsencrypt.json b/test/data_bags/accounts/letsencrypt.json new file mode 100644 index 000000000..2b4005a8d --- /dev/null +++ b/test/data_bags/accounts/letsencrypt.json @@ -0,0 +1,6 @@ +{ + "id": "letsencrypt", + "uid": "526", + "comment": "Let's Encrypt", + "manage_home": false +} diff --git a/test/data_bags/chef/keys.json b/test/data_bags/chef/keys.json new file mode 100644 index 000000000..762690dd6 --- /dev/null +++ b/test/data_bags/chef/keys.json @@ -0,0 +1,61 @@ +{ + "id": "keys", + "git": [ + "-----BEGIN RSA PRIVATE KEY-----", + "MIIEowIBAAKCAQEAmQecBvYZEP3owBge2k2rv4fkrUuxsdueVRArHdopTbMaej7H", + "FxGG/3ZhDN9P/okQVBE5NBnT1wGmeFVS7q+V+grPfp3C9tQiDBghdb5gAR+loSSm", + "7aYTYJ3ngy5DzMD7Jr8cpvl3x76Z/p3/JgFjR8nkjsk81vsj612drQ2YX3H3jvLp", + "ULAqxl6VDOv7Lx1SknE0PglTSemnq6O07R6zyLTc5ZUk1XD2KL1sRANluJg+q3ml", + "RllDNX0j8B3Sz0yttP5/GFRdc+82WS8+86eg2y2vQPpTo86mBwYzkdbG4WT2YIDw", + "w3J93TEL4CAuj0qm4DdDbhnMLkVtHE3uHBQfbQIDAQABAoIBAAXfX+J4gT/ArrTo", + "eA6immuwOFtaI0iTCAF1rGHp5Fnh/KsiS5ucBZU6IsFOtJAtDF5dhtd0Akgm/Q9z", + "HsqgHF5LGele/oNgYqoaJvaQxrhkUYPclzdZfzbe+Gp1VQ6/fSPgg6X4vtsAeoJl", + "58u6k+fGXKoKGPabDqbSoeFpOya2drudqVCZrM++5MchuuRFDKI+/gSsjJKUvAX5", + "l9mA1GvAI6pmYhIaSPe6ywLvtpDpRtRRaCg51IkIi+AdW7iuwaljhCTbo4u6Sp4m", + "ujCxY2n8j5l9QRnnT3Ax9289fgo6+IyRGQpijIBTMVI42ogBmUhC5xpJ+RNl55yy", + "LU5O1/UCgYEAxkny8NS2JT3swOYdeCNhy6ap7MoEkb78MLjuv7tb2/td16GVPcsJ", + "VryTX1O0dKH2K19GMm/RHfIUAGpZou7WbK9dKjA52nOOvipY5V0jMDfeTO9gSM/l", + "urMf0l4e5MK432E7/hxiCy6pBp6cIiHYkf1XGVe55+6BDbjJas3j0DcCgYEAxZGB", + "WhLUUVK63DC690oIl2F8IbktclZfgka41/iWkoa+MUkViSQ5GQ8nOg8TpjWx4Q4V", + "4Z9C9ALeNKhZ1XvYchHTqcLE3aiMnF/3ZY5qxzjxX3aq+yLbPqRFsS/1pA58k6Jf", + "Jc2vJiYqNPPvQfuhypgku7pzf/LrDS8EDf6tE3sCgYAFYe6BURTcr/CkT9rO7w7x", + "i0WjktxK5IdN/0cj1z8oGouylcVKVx+axiWt+cS1Qcw/4ycxqU1g5bhbRofGX3tc", + "meoKgiKf5nEigl3FZCDXZzzWk8zmTRZsWf5sJHfsN8jy713EiRq0OQEHl/ifCJIr", + "bFgX7QSz4gqIx9JX3tznQwKBgCDeRy3MCiSJZerx9HjliS5eGn+lxgjKk9MhnujX", + "Q32XCxc5+Go7a4BexADltzgkoLY3WK6Th1j/DSanh2J72xOHIbaRX50cyF/Pm2H6", + "4orIT2e5X1KuhtkSDUIgH3aurk0Fa1znribjnIv4tSo+CbmhvCK7LzHvIOmtk7gc", + "UYD3AoGBAKxnmdIw+07fThOjpMIZ3BQKF3lRDNG4YWDR/JKBtfN6km64yuBxe26O", + "z12O6hHdn2qRwVTAB9Q/2IgXad7erLpHMkxau+buOdUDYplRsv1rlo57ZUQn+pOD", + "gUvWf4G25mBqtPGDlsb5OufkXc3NfrsDj/nAlZ3Wk//QXbB3xRd7", + "-----END RSA PRIVATE KEY-----" + ], + "letsencrypt": [ + "-----BEGIN RSA PRIVATE KEY-----", + "MIIEowIBAAKCAQEAuUPGzcK1w1NRZtLV1MlbK2D5+Qw/GBn1h/dpr/YjYDxzqyaS", + "4OPXwfwl2NJ6VXGzPnmzgkQaKu1qxpmZ/jqQ26+XQPPrnrd4kBscNvGEStbkwrsp", + "mz5YsGfyG3+Uz625C5OOyXmkRXq7J2gzCrmO5u+RWs0Aj6As21G18eA61QPRthzz", + "P5R4/Un+zN4+kEOTVVYBTop45QPtgi8opc3OZcla2bZbCxuqvU0bb/67mUo+ndjx", + "NtSmKC4wl2q8wIfblycC+abvEIG1Jog64G0fbAGG5oz8Ol8W6q5B2tfpicpigETk", + "Ob4dp5bXcALCll/IyUxVxjQURqM2zN+ZPx33uwIDAQABAoIBAQCOFNmhscMeIoba", + "ObV+NFJ0KTJseqTkwfvYo7ltFnK4+oOm7bVVPceZYNxRtdHWN5XEwycVL092PpBV", + "8TT1kUrJAJgaWzcHiSOwOOphhMX1c2sLoOhew+jWmVFHH4gr4cp5g1fNUjnWgzKH", + "HVWP0xEyMOaj4Xadr7TXGopUDqhv9egN1Sw7091Njk+b5i5waJ7LTfQXDJ53rcoJ", + "1QhoLn8kxeBea/gRQ3FLpghPeEqXnjnyvPBMk3mDiDtwubUkuEs1P0c9znfaRW+m", + "EGFQUAOYnBGOsGkty+0Na8U+Uuj1uGODWPHXQr9p8+AZR3fF7e/9efBvlVAH5KY7", + "4vaYhOtRAoGBAOkfTyaZK2vT7nSC1ubgGJduxK/8kifP+whfiUlvGEMIzUDFbqST", + "L1hCjxOy4FQg+XY+8vx5iwluZhb1sVp4BEeepTPmqDp1AyHMRYy3pZz9zmOYbysN", + "RpglX2GO6XLUnxSFCjf+XcaVzLiVU6XWDYBvvYGBNYLO8bhJ97rwrOcjAoGBAMty", + "JoqmTDzAk+8BV3cci5RbaB7AtmKNEeQ3Ih6Rz3fb8rqxgHnyhNtasWDe84RvykSU", + "LlfpHhVc9zbePkrGZTtZAmzc0qjOqp5lzAHI1UMdvacEVV3UZgHsoVHhFMPEj0bk", + "hoh5opy2lyu9YkM8LJ9RV7Vua6AQcPmsJBh0mAKJAoGAAQ9dYsWLhv/9s5XsuDwI", + "oJemWU6Cs5+kepNEoorYx2VA2ayMJj9tFa+nyuUjU/6aY8lBfZhn43EXEb+oQMsO", + "6ex0v9mqpilmDD9LiapEHISi7Z0B1GZJDeQNnPnzYcxJtOQt+bc0YfTIa4ZyTOy+", + "PvlDGVWnEqMyQi5D7BuwDZUCgYBSFWprgpE76c9GHVp22nOOlhq6XbK4rIZNd9ky", + "UE5O49VZcgiOK0VjY4IxvYKvKpOHe+n+2jWjFPFBmAW2EboCafVKiwYLyeaZJiVb", + "ivZQsA02986hnvLRT/H+oTvJiOLuDYIiSkFLzXfM1ApzajHuzdj/gN+3oyqR8dxW", + "aaRzUQKBgEw7U0vPikteurPmdmBkxYZTnK3o0uhfbiJTPuaSwahlRrxXYolndDoj", + "IvQrZQp5W1+lXpSbAfJCeO7LxH4kjXtboIGUrBviiTALSVnmF8vX1qd1vEVAFh6I", + "Q8pw9G2XqjNjlY/3mL4LLCPwjKlLsZDAeYn9AfUffbQiT8ZjhXMv", + "-----END RSA PRIVATE KEY-----" + ] +} -- 2.43.2