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]
--- /dev/null
+~FC001
+~FC003
+~FC064
+~FC065
--- /dev/null
+# Letsencrypt Cookbook
+
+This cookbook provides lightweight resources to obtain and
+manage letsencrypt certficates.
--- /dev/null
+#!/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
--- /dev/null
+#!/bin/sh
+
+domains=($RENEWED_DOMAINS)
+
+exec /srv/acme.openstreetmap.org/bin/upload "${domains[0]}" "$RENEWED_LINEAGE"
--- /dev/null
+#!/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)
--- /dev/null
+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"
--- /dev/null
+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"
--- /dev/null
+#
+# 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
--- /dev/null
+# DO NOT EDIT - This file is being maintained by Chef
+
+<VirtualHost *:80>
+ 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
+</VirtualHost>
+
+<VirtualHost *:443>
+ 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
+</VirtualHost>
+
+<Directory /srv/acme.openstreetmap.org/html>
+ Require all granted
+</Directory>
--- /dev/null
+0 */12 * * * letsencrypt /srv/acme.openstreetmap.org/renew
--- /dev/null
+#!/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 %>
~FC001
+~FC003
~FC064
~FC065
--- /dev/null
+#
+# 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
--- /dev/null
+[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 -%>
"role[planet]",
"role[planetdump]",
"role[logstash]",
+ "role[letsencrypt]",
"recipe[rsyncd]",
"recipe[openvpn]",
"recipe[git::server]",
--- /dev/null
+name "letsencrypt"
+description "Role applied to all letsencrypt servers"
+
+default_attributes(
+ :accounts => {
+ :users => {
+ :letsencrypt => {
+ :status => :role
+ }
+ }
+ }
+)
+
+run_list(
+ "recipe[letsencrypt]"
+)
--- /dev/null
+{
+ "id": "letsencrypt",
+ "uid": "526",
+ "comment": "Let's Encrypt",
+ "manage_home": false
+}
--- /dev/null
+{
+ "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-----"
+ ]
+}