diff --git a/site-cookbooks/kosmos-ejabberd/.kitchen.yml b/site-cookbooks/kosmos-ejabberd/.kitchen.yml deleted file mode 100644 index 1650f7d..0000000 --- a/site-cookbooks/kosmos-ejabberd/.kitchen.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -driver: - name: vagrant - -provisioner: - name: chef_zero - # You may wish to disable always updating cookbooks in CI or other testing environments. - # For example: - # always_update_cookbooks: <%= !ENV['CI'] %> - always_update_cookbooks: true - -verifier: - name: inspec - -platforms: - - name: ubuntu-16.04 - - name: ubuntu-18.04 - -suites: - - name: default - run_list: - - recipe[kosmos-ejabberd::default] - attributes: diff --git a/site-cookbooks/kosmos-ejabberd/Berksfile b/site-cookbooks/kosmos-ejabberd/Berksfile index abfa26f..dfda6fe 100644 --- a/site-cookbooks/kosmos-ejabberd/Berksfile +++ b/site-cookbooks/kosmos-ejabberd/Berksfile @@ -1,6 +1,34 @@ -# frozen_string_literal: true source 'https://supermarket.chef.io' -source chef_repo: ".." -cookbook "kosmos_postgresql", path: "../kosmos_postgresql" +# community cookbooks pinned to the versions vendored for production (see the +# root Berksfile.lock), so the integration suite exercises the same cookbook set +cookbook 'apt', '= 7.3.0' +cookbook 'build-essential', '= 8.2.1' +cookbook 'firewall', '= 6.2.16' +cookbook 'hostname', '= 0.4.2' +cookbook 'hostsfile', '= 3.0.1' +cookbook 'logrotate', '= 2.2.0' +cookbook 'mysql', '= 8.7.4' +cookbook 'nginx', '= 9.0.0' +cookbook 'ntp', '= 3.4.0' +cookbook 'ohai', '= 5.2.5' +cookbook 'openssl', '= 8.5.5' +cookbook 'postfix', '= 6.4.1' +cookbook 'timezone_iii', '= 1.0.4' +cookbook 'ulimit', '= 1.0.0' +cookbook 'users', '= 5.3.1' +cookbook 'yum', '= 7.4.13' +cookbook 'yum-epel', '= 4.2.3' + +# local cookbooks +cookbook 'kosmos-base', path: '../kosmos-base' +cookbook 'kosmos-nginx', path: '../kosmos-nginx' +cookbook 'kosmos-dirsrv', path: '../kosmos-dirsrv' +cookbook 'kosmos_postgresql', path: '../kosmos_postgresql' +cookbook 'kosmos-postfix', path: '../kosmos-postfix' +cookbook 'kosmos_encfs', path: '../kosmos_encfs' +cookbook 'postgresql', path: '../postgresql' +cookbook 'backup', path: '../backup' +cookbook 'tor-full', path: '../tor-full' + metadata diff --git a/site-cookbooks/kosmos-ejabberd/attributes/default.rb b/site-cookbooks/kosmos-ejabberd/attributes/default.rb index 658d2c3..a17c9e3 100644 --- a/site-cookbooks/kosmos-ejabberd/attributes/default.rb +++ b/site-cookbooks/kosmos-ejabberd/attributes/default.rb @@ -1,6 +1,6 @@ -node.default["ejabberd"]["version"] = "25.08" +node.default["ejabberd"]["version"] = "26.09" node.default["ejabberd"]["package_version"] = "1" -node.default["ejabberd"]["checksum"] = "e4703bc41b5843fc4b76e8b54a9380d5895f9b3dcd4795e05ad0c260ed9b9a23" +node.default["ejabberd"]["checksum"] = "cff7b46d7a614f4c345c1cd7230b1d355a7c3e1f0062e6fbb514038177e4049c" node.default["ejabberd"]["turn_domain"] = "turn.kosmos.org" node.default["ejabberd"]["stun_auth_realm"] = "kosmos.org" node.default["ejabberd"]["stun_turn_port"] = 3478 diff --git a/site-cookbooks/kosmos-ejabberd/kitchen.yml b/site-cookbooks/kosmos-ejabberd/kitchen.yml new file mode 100644 index 0000000..00f6e6d --- /dev/null +++ b/site-cookbooks/kosmos-ejabberd/kitchen.yml @@ -0,0 +1,78 @@ +--- +driver: + name: dokken + chef_version: 18.2.7 + pull_platform_image: false + pull_chef_image: false + memory_limit: 2147483648 # 2GB + volumes: + # saves the apt archives outside of the container + - /var/cache/apt/archives/:/var/cache/apt/archives/ + +transport: + name: dokken + +provisioner: + name: dokken + client_rb: + # the ejabberd package starts its service as ejabberd@localhost during + # installation; keep the chef node name in sync so ERLANG_NODE matches + node_name: localhost + # skip the firewall recipe, which is not wanted inside the container + environment: development + +verifier: + name: inspec + +# prepare the backing services the recipe expects in production: a PostgreSQL +# server reachable as pg.kosmos.local, trusting local connections, with the +# databases used by the vhosts +lifecycle: + pre_converge: + - remote: | + bash -c ' + set -e + grep -q pg.kosmos.local /etc/hosts || echo 127.0.0.1 pg.kosmos.local >> /etc/hosts + systemctl start postgresql + HBA=$(ls /etc/postgresql/*/main/pg_hba.conf | head -1) + grep -q "127.0.0.1/32 trust" "$HBA" || sed -i "1i host all all 127.0.0.1/32 trust" "$HBA" + systemctl reload postgresql + sudo -u postgres psql -c "CREATE ROLE ejabberd LOGIN CREATEDB" 2>/dev/null || true + sudo -u postgres createdb -O ejabberd ejabberd 2>/dev/null || true + sudo -u postgres createdb -O ejabberd ejabberd_5apps 2>/dev/null || true + ' + +platforms: + - name: ubuntu-24.04 + driver: + image: dokken/ubuntu-24.04 + privileged: true + pid_one_command: /usr/lib/systemd/systemd + intermediate_instructions: + # prevent APT from deleting the APT folder + - RUN rm /etc/apt/apt.conf.d/docker-clean + # let packages start their services during installation, like on a real + # node (dokken images ship policy-rc.d that blocks service starts) + - RUN rm -f /usr/sbin/policy-rc.d + - RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql + # provide the TLS material the ejabberd config expects: dhparams.pem is + # generated manually on real nodes, the certs are deployed by the + # letsencrypt recipe (not exercised in this suite) + - RUN mkdir -p /opt/ejabberd/conf && openssl dhparam -out /opt/ejabberd/conf/dhparams.pem 1024 && (for d in kosmos.org kosmos.chat 5apps.com; do openssl req -x509 -newkey rsa:2048 -nodes -days 3650 -subj "/CN=$d" -keyout /opt/ejabberd/conf/$d.key -out /opt/ejabberd/conf/$d.crt; done) + +suites: + - name: default + data_bags_path: "test/integration/default/data_bags" + encrypted_data_bag_secret_key_path: "test/integration/default/encrypted_data_bag_secret" + run_list: + - recipe[kosmos-ejabberd::default] + verifier: + inspec_tests: + - test/integration/default + attributes: + # normally provided by knife-zero / the garage role on real nodes + knife_zero: + host: "127.0.0.1" + garage: + xmpp_upload_bucket: "kosmos-xmpp-uploads" + s3_api_root_domain: "s3.kosmos.org" diff --git a/site-cookbooks/kosmos-ejabberd/recipes/default.rb b/site-cookbooks/kosmos-ejabberd/recipes/default.rb index 548b588..b6bcb61 100644 --- a/site-cookbooks/kosmos-ejabberd/recipes/default.rb +++ b/site-cookbooks/kosmos-ejabberd/recipes/default.rb @@ -42,6 +42,8 @@ file "/opt/ejabberd/.erlang.cookie" do owner "ejabberd" group "ejabberd" content ejabberd_credentials['erlang_cookie'] + # a changed cookie is only picked up by a restart, not by a config reload + notifies :restart, "service[ejabberd]", :delayed end ejabberd_nodes = search(:node, "role:ejabberd") @@ -71,7 +73,8 @@ template "/opt/ejabberd/conf/ejabberdctl.cfg" do owner 'ejabberd' group 'ejabberd' variables epmd_node_name: "ejabberd@#{node['name']}" - notifies :reload, "service[ejabberd]", :delayed + # the Erlang node name is only applied by a restart, not by a config reload + notifies :restart, "service[ejabberd]", :delayed end postgresql_data_bag_item = data_bag_item('credentials', 'postgresql') diff --git a/site-cookbooks/kosmos-ejabberd/templates/ejabberd.yml.erb b/site-cookbooks/kosmos-ejabberd/templates/ejabberd.yml.erb index f1b9fd9..3ee18db 100644 --- a/site-cookbooks/kosmos-ejabberd/templates/ejabberd.yml.erb +++ b/site-cookbooks/kosmos-ejabberd/templates/ejabberd.yml.erb @@ -69,7 +69,6 @@ listen: request_handlers: "/api": mod_http_api tls: false - captcha: false - port: 5443 ip: "::" @@ -87,7 +86,6 @@ listen: tls: true ## "/pub/archive": mod_http_fileserver ## register: true - captcha: false s2s_use_starttls: optional @@ -280,7 +278,6 @@ modules: mod_stream_mgmt: {} mod_s2s_dialback: {} mod_http_api: {} - mod_muc_occupantid: {} mod_muc_rtbl: {} mod_s3_upload: region: <%= @mod_s3_upload[:region] %> diff --git a/site-cookbooks/kosmos-ejabberd/test/integration/default/data_bags/credentials/ejabberd.json b/site-cookbooks/kosmos-ejabberd/test/integration/default/data_bags/credentials/ejabberd.json new file mode 100644 index 0000000..43d7cba --- /dev/null +++ b/site-cookbooks/kosmos-ejabberd/test/integration/default/data_bags/credentials/ejabberd.json @@ -0,0 +1,59 @@ +{ + "id": "ejabberd", + "5apps_ldap_password": { + "encrypted_data": "NwXrlbLC09jE+gKw+PuGaNqO8e1rFHvkTIW7xNfbErepZPOzSgSASYsHYQ==\n", + "iv": "Cgd04Lihul511GU/\n", + "auth_tag": "VWTzs8TyCCTdVk5fdZrCfg==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "kosmos_ldap_password": { + "encrypted_data": "F+4J2sNC+2T8zdXs02YLBTG0nNBKP8Kv/IReYwGVGVfsQiR2aqZG3rzzNwg=\n", + "iv": "C4vHPPj4JFkoI0jh\n", + "auth_tag": "M7qic/or/YPjXyFjEtyOGg==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "uploads_secret": { + "encrypted_data": "esr0B8owTy9rTW/2aFG+vBcJh+PWncxiZ8KeGi5Plhu4P8TvRrY=\n", + "iv": "aeEissRFlaFhZrN0\n", + "auth_tag": "/P6M0JbhZ+ICmb0g+V1P9A==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "admins": { + "encrypted_data": "EImT0OiChdJIJbKzABc02aQ009BBZUtO7tv7NUVdKbdaU6YWqg==\n", + "iv": "7qxiQCAUsQVVvdqN\n", + "auth_tag": "g/naI1qKKTyMWRREokXyKg==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "erlang_cookie": { + "encrypted_data": "5teN102XDkxzX+yLYMaQveFJHRObMqLyNjlkytgTMvTWFJNZKQ==\n", + "iv": "paMgCeRvSVXXcoz7\n", + "auth_tag": "tZCz68tYrLHNdoozX3YWmw==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "stun_secret": { + "encrypted_data": "6hOSI4CSpMimyE3BTcNzCe47AOA+EDy+cNrIDrnLZVlPC+o=\n", + "iv": "qSlTvzVyA1HaYqPL\n", + "auth_tag": "ODub1d75qGOH0jWBy/vQAg==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "s3_key_id": { + "encrypted_data": "2LVJNkyPIyxaeU7MxaCQpufbycPY15C1wmoDCpn1uiQ/\n", + "iv": "WwEKzWDfpY7W/Gfn\n", + "auth_tag": "P0UyvGVJXtKS4cfjt5PMDw==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "s3_secret_key": { + "encrypted_data": "sFKFQInT+k/+gJHTLTl4WvATypyKMyRr96ZxVXST2ZKxo0lBFg==\n", + "iv": "xXN7at49ot0Xtid/\n", + "auth_tag": "ap2t6UtA83du/wNNe2sKLg==\n", + "version": 3, + "cipher": "aes-256-gcm" + } +} diff --git a/site-cookbooks/kosmos-ejabberd/test/integration/default/data_bags/credentials/postgresql.json b/site-cookbooks/kosmos-ejabberd/test/integration/default/data_bags/credentials/postgresql.json new file mode 100644 index 0000000..37025b4 --- /dev/null +++ b/site-cookbooks/kosmos-ejabberd/test/integration/default/data_bags/credentials/postgresql.json @@ -0,0 +1,17 @@ +{ + "id": "postgresql", + "ejabberd_user_password": { + "encrypted_data": "xijtK84xBV9O6NQPNMZ1RcDYLDsl8ZzV7ebbLBrK2SJ9aWp3txhqaP2t5D8=\n", + "iv": "Z/ykF64bhUktEH3L\n", + "auth_tag": "1CXtZlzSCCtjVkCDXUX2Kg==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "server_password": { + "encrypted_data": "tzeOnply5i9Efg/CcVKdpom3qPKZ/0espjFhz5EaVmaZQ2CWWsAnQcA=\n", + "iv": "VgZE26GohviDaKRH\n", + "auth_tag": "lFl6lPg/0bKT+706olGKuA==\n", + "version": 3, + "cipher": "aes-256-gcm" + } +} diff --git a/site-cookbooks/kosmos-ejabberd/test/integration/default/default_test.rb b/site-cookbooks/kosmos-ejabberd/test/integration/default/default_test.rb new file mode 100644 index 0000000..bb636e8 --- /dev/null +++ b/site-cookbooks/kosmos-ejabberd/test/integration/default/default_test.rb @@ -0,0 +1,43 @@ +# Chef InSpec test for recipe kosmos-ejabberd::default + +# The Chef InSpec reference, with examples and extensive documentation, can be +# found at https://docs.chef.io/inspec/resources/ + +describe package('ejabberd') do + it { should be_installed } +end + +describe service('ejabberd') do + it { should be_enabled } + it { should be_running } +end + +describe port(5222) do + it { should be_listening } +end + +describe port(5269) do + it { should be_listening } +end + +describe file('/opt/ejabberd/conf/ejabberd.yml') do + it { should exist } + its('mode') { should cmp '0640' } + + # BOSH and S2S are intentionally enabled and are fixed in 26.09. They must + # keep working, so guard the handlers against accidental removal. + its('content') { should match(/mod_bosh:/) } + its('content') { should match(%r{"/bosh": mod_bosh}) } + its('content') { should match(/ejabberd_s2s_in/) } + + # Regression guard for the unauthenticated RCE fixed in 26.09: mod_adhoc_api + # is one of the exploit prerequisites and must never be enabled. + its('content') { should_not match(/mod_adhoc_api:/) } + + # mod_muc_occupantid was merged into mod_muc in 26.02 and the standalone + # module removed, so it must not be configured anymore. + its('content') { should_not match(/mod_muc_occupantid:/) } + + # the listen option 'captcha' was deprecated in 26.09 + its('content') { should_not match(/^\s*captcha:/) } +end diff --git a/site-cookbooks/kosmos-ejabberd/test/integration/default/encrypted_data_bag_secret b/site-cookbooks/kosmos-ejabberd/test/integration/default/encrypted_data_bag_secret new file mode 100644 index 0000000..2eb949a --- /dev/null +++ b/site-cookbooks/kosmos-ejabberd/test/integration/default/encrypted_data_bag_secret @@ -0,0 +1 @@ +GrIN04mao5nI69WUO4h7IKYsOCXtGiKSOa1zeBYbxso= \ No newline at end of file diff --git a/site-cookbooks/kosmos-ejabberd/test/integration/default/environments/development.json b/site-cookbooks/kosmos-ejabberd/test/integration/default/environments/development.json new file mode 100644 index 0000000..0956f19 --- /dev/null +++ b/site-cookbooks/kosmos-ejabberd/test/integration/default/environments/development.json @@ -0,0 +1,12 @@ +{ + "name": "development", + "description": "development environment used by the Test Kitchen suite", + "json_class": "Chef::Environment", + "chef_type": "environment", + "default_attributes": {}, + "override_attributes": { + "kosmos-dirsrv": { + "master_hostname": "localhost" + } + } +} diff --git a/site-cookbooks/kosmos-ejabberd/test/integration/default/serverspec/default_spec.rb b/site-cookbooks/kosmos-ejabberd/test/integration/default/serverspec/default_spec.rb deleted file mode 100644 index 703630b..0000000 --- a/site-cookbooks/kosmos-ejabberd/test/integration/default/serverspec/default_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'serverspec' - -# Required by serverspec -set :backend, :exec - -describe 'ejabberd' do - describe package('ejabberd') do - it { should be_installed } - end - - it 'is listening on port 5222 (client-to-server)' do - expect(port(5222)).to be_listening - end - - it 'is listening on port 5269 (server-to-server)' do - expect(port(5269)).to be_listening - end - - it 'runs the ejabberd service' do - expect(service('ejabberd')).to be_running - expect(service('ejabberd')).to be_enabled - end -end