From 91ffe75bc1ed4df8a0a6507d2acbcd63c0aa9be9 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Mon, 29 Nov 2021 13:12:17 -0600 Subject: [PATCH] Add missing files --- clients/postgres-4.json | 4 + nodes/postgres-4.json | 57 ++++++++++ site-cookbooks/kosmos_postgresql/.gitignore | 22 ++++ site-cookbooks/kosmos_postgresql/Berksfile | 4 + site-cookbooks/kosmos_postgresql/CHANGELOG.md | 5 + site-cookbooks/kosmos_postgresql/LICENSE | 20 ++++ site-cookbooks/kosmos_postgresql/README.md | 57 ++++++++++ .../kosmos_postgresql/attributes/default.rb | 3 + site-cookbooks/kosmos_postgresql/chefignore | 104 ++++++++++++++++++ .../kosmos_postgresql/libraries/helpers.rb | 45 ++++++++ site-cookbooks/kosmos_postgresql/metadata.rb | 25 +++++ .../kosmos_postgresql/recipes/firewall.rb | 15 +++ .../kosmos_postgresql/recipes/hostsfile.rb | 16 +++ .../kosmos_postgresql/recipes/primary.rb | 33 ++++++ .../kosmos_postgresql/recipes/replica.rb | 56 ++++++++++ .../kosmos_postgresql/resources/server.rb | 78 +++++++++++++ 16 files changed, 544 insertions(+) create mode 100644 clients/postgres-4.json create mode 100644 nodes/postgres-4.json create mode 100644 site-cookbooks/kosmos_postgresql/.gitignore create mode 100644 site-cookbooks/kosmos_postgresql/Berksfile create mode 100644 site-cookbooks/kosmos_postgresql/CHANGELOG.md create mode 100644 site-cookbooks/kosmos_postgresql/LICENSE create mode 100644 site-cookbooks/kosmos_postgresql/README.md create mode 100644 site-cookbooks/kosmos_postgresql/attributes/default.rb create mode 100644 site-cookbooks/kosmos_postgresql/chefignore create mode 100644 site-cookbooks/kosmos_postgresql/libraries/helpers.rb create mode 100644 site-cookbooks/kosmos_postgresql/metadata.rb create mode 100644 site-cookbooks/kosmos_postgresql/recipes/firewall.rb create mode 100644 site-cookbooks/kosmos_postgresql/recipes/hostsfile.rb create mode 100644 site-cookbooks/kosmos_postgresql/recipes/primary.rb create mode 100644 site-cookbooks/kosmos_postgresql/recipes/replica.rb create mode 100644 site-cookbooks/kosmos_postgresql/resources/server.rb diff --git a/clients/postgres-4.json b/clients/postgres-4.json new file mode 100644 index 0000000..eed891f --- /dev/null +++ b/clients/postgres-4.json @@ -0,0 +1,4 @@ +{ + "name": "postgres-4", + "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6fPxOZeKloF/EgYvU0k\nOwv8bJjsCQcWaMTPle5//mRTszA6PM2z9RI+Mfr45qxTlsL9pQY8WJOWF6QOK31x\nszuqcr7oOjtAhrLI8f/oNDEDjcx325FqG9gNKQEAD7d4zodh+PhDe6x7GIyIS7lG\nIcD5Zre9iDwv8FGLR+5GLqS8SJOPL/wJkQ8w+N0f8YDFw81kiTta5NLhAx3fMDs0\n2kmoNlbmKlNZTtLjCfCV+/pa9oY6wycjck3GvobiFE/4cWaNkeGlPc+uAwlfmrOv\nHy0tq1XBX/BCvE5kMXmhnMT23JXjm2s2PgCLgEVGAXilXk/T597KDm+z4oBpAQma\nnQIDAQAB\n-----END PUBLIC KEY-----\n" +} \ No newline at end of file diff --git a/nodes/postgres-4.json b/nodes/postgres-4.json new file mode 100644 index 0000000..77e5a0f --- /dev/null +++ b/nodes/postgres-4.json @@ -0,0 +1,57 @@ +{ + "name": "postgres-4", + "normal": { + "knife_zero": { + "host": "10.1.1.107" + } + }, + "automatic": { + "fqdn": "postgres-4", + "os": "linux", + "os_version": "5.4.0-91-generic", + "hostname": "postgres-4", + "ipaddress": "192.168.122.3", + "roles": [ + "postgresql_replica" + ], + "recipes": [ + "kosmos-base", + "kosmos-base::default", + "kosmos_postgresql::hostsfile", + "kosmos_postgresql::replica", + "kosmos_postgresql::firewall", + "apt::default", + "timezone_iii::default", + "timezone_iii::debian", + "ntp::default", + "ntp::apparmor", + "kosmos-base::systemd_emails", + "apt::unattended-upgrades", + "kosmos-base::firewall", + "kosmos-postfix::default", + "postfix::default", + "postfix::_common", + "postfix::_attributes", + "postfix::sasl_auth", + "hostname::default" + ], + "platform": "ubuntu", + "platform_version": "20.04", + "cloud": null, + "chef_packages": { + "chef": { + "version": "17.7.29", + "chef_root": "/opt/chef/embedded/lib/ruby/gems/3.0.0/gems/chef-17.7.29/lib", + "chef_effortless": null + }, + "ohai": { + "version": "17.7.8", + "ohai_root": "/opt/chef/embedded/lib/ruby/gems/3.0.0/gems/ohai-17.7.8/lib/ohai" + } + } + }, + "run_list": [ + "recipe[kosmos-base]", + "role[postgresql_replica]" + ] +} \ No newline at end of file diff --git a/site-cookbooks/kosmos_postgresql/.gitignore b/site-cookbooks/kosmos_postgresql/.gitignore new file mode 100644 index 0000000..13e41c4 --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/.gitignore @@ -0,0 +1,22 @@ +.vagrant +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ + +# Bundler +Gemfile.lock +gems.locked +bin/* +.bundle/* + +# test kitchen +.kitchen/ +.kitchen.local.yml + +# Chef +Berksfile.lock +.zero-knife.rb +Policyfile.lock.json diff --git a/site-cookbooks/kosmos_postgresql/Berksfile b/site-cookbooks/kosmos_postgresql/Berksfile new file mode 100644 index 0000000..0656a99 --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/Berksfile @@ -0,0 +1,4 @@ +# frozen_string_literal: true +source 'https://supermarket.chef.io' + +metadata diff --git a/site-cookbooks/kosmos_postgresql/CHANGELOG.md b/site-cookbooks/kosmos_postgresql/CHANGELOG.md new file mode 100644 index 0000000..1ffdd2a --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/CHANGELOG.md @@ -0,0 +1,5 @@ +# kosmos_postgresql CHANGELOG + +# 0.1.0 + +Initial release. diff --git a/site-cookbooks/kosmos_postgresql/LICENSE b/site-cookbooks/kosmos_postgresql/LICENSE new file mode 100644 index 0000000..c150a1f --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2019-2020 Kosmos Developers + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/site-cookbooks/kosmos_postgresql/README.md b/site-cookbooks/kosmos_postgresql/README.md new file mode 100644 index 0000000..9ea16a7 --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/README.md @@ -0,0 +1,57 @@ +# kosmos_postgresql + +## Usage + +### On the primary: + +Set the `postgresql_primary` role on the node + +### On the replica: + +Add the `postgresql_replica` role to the node's run list. Run Chef on the node +a first time. +After the initial Chef run on the replica, run Chef on the primary to add the +firewall rules and PostgreSQL access rules, then run Chef again on the replica +to set up replication. + +## Caveat + +[`firewall_rules`](https://github.com/chef-cookbooks/firewall/issues/134) and +[`postgresql_access`](https://github.com/sous-chefs/postgresql/issues/648) are +declared in recipes, not resources because of the way custom resources +work currently in Chef. See the `default.rb` and `replica.rb` recipes. + +The primary gives access to the `replication` db to the `replication` user +connecting from a replica, and replicas to the primary. For more information +about PostgreSQL client authentication, see the +[official docs](https://www.postgresql.org/docs/12/auth-pg-hba-conf.html) + +The primary opens up the PostgreSQL port (5432 TCP) to replicas, and replicas +to the primary. + +## TLS self-signed certificate + +A wildcard (`*.kosmos.org` certificate) was generated with the following +commands: + +``` +openssl req -new -nodes -text -out root.csr -keyout root.key \ + -subj "/CN=root.kosmos.org" +chmod og-rwx root.key +openssl x509 -req -in root.csr -text -days 3650 \ + -extfile /etc/ssl/openssl.cnf -extensions v3_ca \ + -signkey root.key -out root.crt +openssl req -new -nodes -text -out server.csr \ + -keyout server.key -subj "/CN=*.kosmos.org" +chmod og-rwx server.key +openssl x509 -req -in server.csr -text -days 1825 \ + -CA root.crt -CAkey root.key -CAcreateserial \ + -out server.crt +``` + +It is valid until May 12 2025. + +The content of `server.crt`, `server.key` and `root.crt` an stored in the +`postgresql` encrypted data bag. The root key is stored in LastPass +("Self-signed TLS root certificate"). `server.crt` & `server.key` are used by +the PostgreSQL server. diff --git a/site-cookbooks/kosmos_postgresql/attributes/default.rb b/site-cookbooks/kosmos_postgresql/attributes/default.rb new file mode 100644 index 0000000..f3daf9b --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/attributes/default.rb @@ -0,0 +1,3 @@ +# This is set to false by default, and set to true in the server resource +# for replicas. +node.default['kosmos_postgresql']['ready_to_set_up_replica'] = false diff --git a/site-cookbooks/kosmos_postgresql/chefignore b/site-cookbooks/kosmos_postgresql/chefignore new file mode 100644 index 0000000..4439807 --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/chefignore @@ -0,0 +1,104 @@ +# Put files/directories that should be ignored in this file when uploading +# to a chef-server or supermarket. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +examples/* +Guardfile +Procfile +.kitchen* +kitchen.yml* +.rubocop.yml +spec/* +Rakefile +.travis.yml +.foodcritic +.codeclimate.yml + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +Berksfile +Berksfile.lock +cookbooks/* +tmp + +# Bundler # +########### +vendor/* + +# Policyfile # +############## +Policyfile.rb +Policyfile.lock.json + +# Cookbooks # +############# +CONTRIBUTING* +CHANGELOG* +TESTING* + +# Vagrant # +########### +.vagrant +Vagrantfile diff --git a/site-cookbooks/kosmos_postgresql/libraries/helpers.rb b/site-cookbooks/kosmos_postgresql/libraries/helpers.rb new file mode 100644 index 0000000..7d3c397 --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/libraries/helpers.rb @@ -0,0 +1,45 @@ +class Chef + class Recipe + def postgresql_primary + postgresql_primary = search(:node, "role:postgresql_primary AND chef_environment:#{node.chef_environment}").first + + unless postgresql_primary.nil? + primary_ip = ip_for(postgresql_primary) + + { hostname: postgresql_primary[:hostname], ipaddress: primary_ip } + end + end + + def postgresql_replicas + postgresql_replicas = [] + + search(:node, "role:postgresql_replica AND chef_environment:#{node.chef_environment}").each do |replica| + replica_ip = ip_for(replica) + + postgresql_replicas << { hostname: replica[:hostname], ipaddress: replica_ip } + end + + postgresql_replicas + end + + def ip_for(server_node) + if node.chef_environment == "development" + server_node['network']['interfaces']['eth1']['routes'].first['src'] + else + # If the server has a private Zerotier IP, use it + if server_node['knife_zero'] && server_node['knife_zero']['host'] && \ + server_node['knife_zero']['host'].start_with?("10.1.1.") + server_node['knife_zero']['host'] + else + server_node['ipaddress'] + end + end + end + + def postgresql_service_name + postgresql_version = "12" + + "postgresql@#{postgresql_version}-main" + end + end +end diff --git a/site-cookbooks/kosmos_postgresql/metadata.rb b/site-cookbooks/kosmos_postgresql/metadata.rb new file mode 100644 index 0000000..ad7ca14 --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/metadata.rb @@ -0,0 +1,25 @@ +name 'kosmos_postgresql' +maintainer 'Kosmos' +maintainer_email 'ops@5apps.com' +license 'MIT' +description 'Installs/Configures kosmos_postgresql' +long_description 'Installs/Configures kosmos_postgresql' +version '0.1.0' +chef_version '>= 12.14' if respond_to?(:chef_version) + +# The `issues_url` points to the location where issues for this cookbook are +# tracked. A `View Issues` link will be displayed on this cookbook's page when +# uploaded to a Supermarket. +# +# issues_url 'https://github.com//kosmos_postgresql/issues' + +# The `source_url` points to the development repository for this cookbook. A +# `View Source` link will be displayed on this cookbook's page when uploaded to +# a Supermarket. +# +# source_url 'https://github.com//kosmos_postgresql' + +depends "postgresql", ">= 7.0.0" +depends "build-essential" +depends "kosmos_encfs" +depends "hostsfile" diff --git a/site-cookbooks/kosmos_postgresql/recipes/firewall.rb b/site-cookbooks/kosmos_postgresql/recipes/firewall.rb new file mode 100644 index 0000000..ebc3404 --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/recipes/firewall.rb @@ -0,0 +1,15 @@ +# +# Cookbook:: kosmos_postgresql +# Recipe:: firewall +# + +unless node.chef_environment == "development" + include_recipe "kosmos-base::firewall" + + firewall_rule "postgresql zerotier members" do + port 5432 + protocol :tcp + command :allow + source "10.1.1.0/24" + end +end diff --git a/site-cookbooks/kosmos_postgresql/recipes/hostsfile.rb b/site-cookbooks/kosmos_postgresql/recipes/hostsfile.rb new file mode 100644 index 0000000..9000b67 --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/recipes/hostsfile.rb @@ -0,0 +1,16 @@ +# +# Cookbook:: kosmos_postgresql +# Recipe:: hostsfile +# + +begin +primary_ip = postgresql_primary[:ipaddress] +rescue NoMethodError +end + +unless primary_ip.nil? + hostsfile_entry primary_ip do + hostname "pg.kosmos.local" + unique true + end +end diff --git a/site-cookbooks/kosmos_postgresql/recipes/primary.rb b/site-cookbooks/kosmos_postgresql/recipes/primary.rb new file mode 100644 index 0000000..de7466f --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/recipes/primary.rb @@ -0,0 +1,33 @@ +# +# Cookbook:: kosmos_postgresql +# Recipe:: primary +# + +postgresql_version = "12" +postgresql_service = "postgresql@#{postgresql_version}-main" + +service postgresql_service do + supports restart: true, status: true, reload: true +end + +postgresql_custom_server postgresql_version do + role "primary" +end + +postgresql_access "zerotier members" do + access_type "host" + access_db "all" + access_user "all" + access_addr "10.1.1.0/24" + access_method "md5" + notifies :reload, "service[#{postgresql_service}]", :immediately +end + +postgresql_access "zerotier members replication" do + access_type "host" + access_db "replication" + access_user "replication" + access_addr "10.1.1.0/24" + access_method "md5" + notifies :reload, "service[#{postgresql_service}]", :immediately +end diff --git a/site-cookbooks/kosmos_postgresql/recipes/replica.rb b/site-cookbooks/kosmos_postgresql/recipes/replica.rb new file mode 100644 index 0000000..b1dd345 --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/recipes/replica.rb @@ -0,0 +1,56 @@ +# +# Cookbook:: kosmos_postgresql +# Recipe:: replica +# + +postgresql_version = "12" +postgresql_service = "postgresql@#{postgresql_version}-main" + +postgresql_custom_server postgresql_version do + role "replica" +end + +service postgresql_service do + supports restart: true, status: true, reload: true +end + +postgresql_data_bag_item = data_bag_item('credentials', 'postgresql') + +primary = postgresql_primary + +unless primary.nil? + # TODO + postgresql_data_dir = "/var/lib/postgresql/#{postgresql_version}/main" + + # FIXME get zerotier IP + execute "set up replication" do + command <<-EOF +systemctl stop #{postgresql_service} +mv #{postgresql_data_dir} #{postgresql_data_dir}.old +pg_basebackup -h pg.kosmos.local -U replication -D #{postgresql_data_dir} -R +chown -R postgres:postgres #{postgresql_data_dir} +systemctl start #{postgresql_service} + EOF + environment 'PGPASSWORD' => postgresql_data_bag_item['replication_password'] + sensitive true + not_if { ::File.exist? "#{postgresql_data_dir}/standby.signal" } + end + + postgresql_access "zerotier members" do + access_type "host" + access_db "all" + access_user "all" + access_addr "10.1.1.0/24" + access_method "md5" + notifies :reload, "service[#{postgresql_service}]", :immediately + end + + postgresql_access "zerotier members replication" do + access_type "host" + access_db "replication" + access_user "replication" + access_addr "10.1.1.0/24" + access_method "md5" + notifies :reload, "service[#{postgresql_service}]", :immediately + end +end diff --git a/site-cookbooks/kosmos_postgresql/resources/server.rb b/site-cookbooks/kosmos_postgresql/resources/server.rb new file mode 100644 index 0000000..5d5fb1c --- /dev/null +++ b/site-cookbooks/kosmos_postgresql/resources/server.rb @@ -0,0 +1,78 @@ +resource_name :postgresql_custom_server +provides :postgresql_custom_server + +property :postgresql_version, String, required: true, name_property: true +property :role, String, required: true # Can be primary or replica + +action :create do + postgresql_version = new_resource.postgresql_version + postgresql_data_dir = "/var/lib/postgresql/#{postgresql_version}/main" + postgresql_service = "postgresql@#{postgresql_version}-main" + postgresql_credentials = data_bag_item('credentials', 'postgresql') + + build_essential do + compile_time true + end + + package("libpq-dev") { action :nothing }.run_action(:install) + + chef_gem 'pg' do + compile_time true + end + + user "postgres" do + manage_home false + end + + postgresql_server_install "main" do + version postgresql_version + setup_repo true + password postgresql_credentials['server_password'] + action :install + end + + service postgresql_service do + supports restart: true, status: true, reload: true + action [:enable, :start] + end + + # This service is a dependency that will auto-start our cluster service on + # boot if it's enabled, so we disable it explicitly + service "postgresql" do + action :disable + end + + shared_buffers = if node['memory']['total'].to_i / 1024 < 1024 # < 1GB RAM + "128MB" + else # >= 1GB RAM, use 50% of total RAM + "#{node['memory']['total'].to_i / 1024 / 2}MB" + end + + additional_config = { + max_connections: 200, # default + shared_buffers: shared_buffers, + unix_socket_directories: "/var/run/postgresql", + dynamic_shared_memory_type: "posix", + timezone: "UTC", # default is GMT + listen_addresses: "0.0.0.0" + } + + additional_config[:promote_trigger_file] = "#{postgresql_data_dir}/failover.trigger" + + postgresql_server_conf "main" do + version postgresql_version + additional_config additional_config + notifies :reload, "service[#{postgresql_service}]", :delayed + end + + postgresql_user "replication" do + action :create + replication true + password postgresql_credentials['replication_password'] + end +end + +action_class do + # to use the data_dir helper + include PostgresqlCookbook::Helpers +end