subspace 3.0.20 → 3.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60535a3d51d04728b59132a9a0db1ca1c4e13fb61a912daa0c507d74af77ba3c
4
- data.tar.gz: ddeb0f458623dd033215b7ee6c72710da921d653d6bbcf43cdee54e7772bfd55
3
+ metadata.gz: e12ca8ad07d7bc9ef0a4eed0d52be0f1ffa6cac408cd0acb52da0cfb994ea9f1
4
+ data.tar.gz: '082166c447bb6dcb58a71599167ed69d23ed3cd9dd13ccb1bd34a9eb1039d7c8'
5
5
  SHA512:
6
- metadata.gz: 5022d270ed1eba11e31d7a61aedb9a8744b40075b0d7b7670be57f6bee4e1f7c3c813bd38b4d86f39fef333701b3f97d47c5e441dc87123cb61aad8d4d5e3884
7
- data.tar.gz: bbefbc1564cac47009312fc388d0786d3de8361f41e21cac4005c3068c62940c021c068cc47512e749a72fbe1b1813b2a2d6f494e5e56550b8ad10a73e548a78
6
+ metadata.gz: 323f2f1acd128d1f4f7c5080bea4263580f77d837a24aa2359d3c4d865c10533978214ea444c365dadb9f8800feb7fa8175f597b0679978bbb33f35994ba1138
7
+ data.tar.gz: 1eb77a6dcfc4df830e6d5e223cc2d3f48ac48444955bc3a56916997b17b412e2920af98624ed3b119d5f583c819a504bc74aa40b822f3135af51b67c14030026
data/CHANGELOG.md CHANGED
@@ -12,6 +12,12 @@ This project attempts to follow [semantic versioning](https://semver.org/).
12
12
 
13
13
  ## Unreleased
14
14
 
15
+ ## 3.0.22
16
+ * Switch nginx from ppa:ondrej/nginx to official nginx.org repository.
17
+
18
+ ## 3.0.21
19
+ * Add gem-patch-report role. Sends stats for each vulnerable gem fixed since the start of the month.
20
+
15
21
  ## 3.0.20
16
22
  * Update postgresql-client role to get the actual psql database version instead of the local client version
17
23
 
@@ -62,6 +62,46 @@
62
62
  tags:
63
63
  - maintenance
64
64
 
65
+ - name: Remove ppa:ondrej/nginx apt repository
66
+ apt_repository:
67
+ repo: ppa:ondrej/nginx
68
+ state: absent
69
+ become: true
70
+
71
+ - name: Install nginx repo prerequisites
72
+ apt:
73
+ pkg:
74
+ - curl
75
+ - gnupg2
76
+ - ca-certificates
77
+ - lsb-release
78
+ - ubuntu-keyring
79
+ state: present
80
+ become: true
81
+
82
+ - name: Import official nginx signing key
83
+ shell: curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor > /usr/share/keyrings/nginx-archive-keyring.gpg
84
+ args:
85
+ creates: /usr/share/keyrings/nginx-archive-keyring.gpg
86
+ become: true
87
+
88
+ - name: Add official nginx.org stable apt repository
89
+ apt_repository:
90
+ repo: "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://nginx.org/packages/ubuntu {{ ansible_distribution_release }} nginx"
91
+ filename: nginx
92
+ state: present
93
+ become: true
94
+
95
+ - name: Pin nginx.org packages over distribution packages
96
+ copy:
97
+ content: |
98
+ Package: *
99
+ Pin: origin nginx.org
100
+ Pin: release o=nginx
101
+ Pin-Priority: 900
102
+ dest: /etc/apt/preferences.d/99nginx
103
+ become: true
104
+
65
105
  - name: apt-get update
66
106
  apt: update_cache=yes cache_valid_time=86400
67
107
  become: true
@@ -84,10 +124,6 @@
84
124
  state: latest
85
125
  update_cache: yes
86
126
 
87
- - name: Add ppa:ondrej/nginx apt repository for TLS 1.3
88
- apt_repository:
89
- repo: ppa:ondrej/nginx
90
-
91
127
  - name: /usr/lib/update-notifier/apt-check --human-readable
92
128
  command: /usr/lib/update-notifier/apt-check --human-readable
93
129
  tags:
@@ -0,0 +1,137 @@
1
+ # Gem Patch Report Role
2
+
3
+ This Ansible role generates reports on Ruby gems that had security vulnerabilities at the beginning of the current month and have since been patched. It compares the Gemfile.lock from the start of the month (retrieved from the Capistrano git repo) against the current Gemfile.lock using bundle-audit, and reports only the gems that have been fixed.
4
+
5
+ ## Requirements
6
+
7
+ - Ruby application deployed with Capistrano (requires the `repo/` directory)
8
+ - bundler-audit gem (automatically installed if missing)
9
+ - Stats server configuration (same as other subspace roles)
10
+
11
+ ## Role Variables
12
+
13
+ Available variables with their default values:
14
+
15
+ ```yaml
16
+ # Path to the Rails/Ruby application (Capistrano current symlink)
17
+ gem_patch_report_app_path: "/u/apps/{{project_name}}/current"
18
+
19
+ # Required for stats reporting (inherited from other roles)
20
+ send_stats: false
21
+ stats_url: ""
22
+ stats_api_key: ""
23
+ hostname: ""
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ 1. Enable the role in your playbook:
29
+ ```yaml
30
+ roles:
31
+ - gem-patch-report
32
+ ```
33
+
34
+ 2. Configure the required variables:
35
+ ```yaml
36
+ send_stats: true
37
+ stats_url: "https://your-stats-server.com/api/stats"
38
+ stats_api_key: "your-api-key"
39
+ ```
40
+
41
+ ## How It Works
42
+
43
+ 1. Retrieves the Gemfile.lock from the last commit before the 1st of the current month using the Capistrano bare git repo at `<deploy_to>/repo/`
44
+ 2. Runs `bundle-audit` against both the old and current Gemfile.lock
45
+ 3. Compares the results to identify vulnerabilities that existed at the start of the month but are no longer present (i.e. patched)
46
+ 4. Outputs a report containing only the patched gems
47
+
48
+ ## Bundle-Audit Input Format
49
+
50
+ The role processes JSON output from `bundle-audit check --format json`. Here's the simplified structure:
51
+
52
+ ```json
53
+ {
54
+ "version": "0.9.2",
55
+ "created_at": "2026-03-06 12:16:58 -0600",
56
+ "results": [
57
+ {
58
+ "type": "unpatched_gem",
59
+ "gem": {
60
+ "name": "nokogiri",
61
+ "version": "1.18.9"
62
+ },
63
+ "advisory": {
64
+ "id": "GHSA-wx95-c6cv-8532",
65
+ "title": "Nokogiri does not check the return value from xmlC14NExecute",
66
+ "criticality": "medium",
67
+ "cve": null,
68
+ "patched_versions": [">= 1.19.1"]
69
+ }
70
+ }
71
+ ]
72
+ }
73
+ ```
74
+
75
+ ### Key Fields Used
76
+
77
+ The role extracts data from these fields:
78
+ - `results[].type` - Filters for `"unpatched_gem"`
79
+ - `results[].gem.name` - Gem name
80
+ - `results[].gem.version` - Vulnerable version
81
+ - `results[].advisory.id` - Advisory ID (CVE, GHSA, etc.)
82
+ - `results[].advisory.criticality` - Severity level
83
+
84
+ ## Report Format
85
+
86
+ The role generates a JSON array of gems that were patched during the current month. If a gem had multiple advisories that were all fixed, each advisory appears as its own entry.
87
+
88
+ ```json
89
+ [
90
+ {
91
+ "name": "activestorage",
92
+ "version": "8.0.2.1",
93
+ "current_version": "8.0.4.1",
94
+ "advisory_id": "CVE-2026-33658",
95
+ "criticality": null
96
+ },
97
+ {
98
+ "name": "bcrypt",
99
+ "version": "3.1.20",
100
+ "current_version": "3.1.22",
101
+ "advisory_id": "CVE-2026-33306",
102
+ "criticality": null
103
+ },
104
+ {
105
+ "name": "devise",
106
+ "version": "4.9.4",
107
+ "current_version": "5.0.3",
108
+ "advisory_id": "CVE-2026-32700",
109
+ "criticality": null
110
+ }
111
+ ]
112
+ ```
113
+
114
+ ### Gem Objects
115
+
116
+ Each gem object contains:
117
+ - `name`: The gem name
118
+ - `version`: The vulnerable version from the start of the month
119
+ - `current_version`: The current patched version (parsed from the current Gemfile.lock)
120
+ - `advisory_id`: The security advisory ID (CVE, GHSA, etc.)
121
+ - `criticality`: The severity level (low, medium, high, critical, or null if not specified)
122
+
123
+ ## Tags
124
+
125
+ This role supports the following tags:
126
+ - `maintenance`
127
+ - `stats`
128
+ - `gem-patch-report`
129
+
130
+ ## Error Handling
131
+
132
+ The role includes error handling for common scenarios:
133
+ - Missing Gemfile.lock
134
+ - No git history before the current month
135
+ - bundle-audit execution failures
136
+
137
+ Errors are logged but don't fail the entire playbook execution. If no patched gems are found, an empty array `[]` is reported.
@@ -0,0 +1,2 @@
1
+ ---
2
+ gem_patch_report_app_path: "/u/apps/{{project_name}}/current"
@@ -0,0 +1,89 @@
1
+ ---
2
+ - name: Check if gem patch reporting is enabled and required variables are set
3
+ debug:
4
+ msg: "Gem patch reporting is enabled"
5
+ when: send_stats == true and stats_url is defined and stats_api_key is defined
6
+ tags:
7
+ - maintenance
8
+ - stats
9
+ - gem-patch-report
10
+
11
+ - name: Install bundler-audit gem globally
12
+ gem:
13
+ name: bundler-audit
14
+ state: present
15
+ user_install: false
16
+ become: true
17
+ when: send_stats == true and stats_url is defined and stats_api_key is defined
18
+ tags:
19
+ - maintenance
20
+ - stats
21
+ - gem-patch-report
22
+
23
+ - name: Generate gem patch report script
24
+ template:
25
+ src: gem-patch-report.sh.j2
26
+ dest: /tmp/gem-patch-report.sh
27
+ mode: '0755'
28
+ when: send_stats == true and stats_url is defined and stats_api_key is defined
29
+ tags:
30
+ - maintenance
31
+ - stats
32
+ - gem-patch-report
33
+
34
+ - name: Run gem patch report script
35
+ shell: /tmp/gem-patch-report.sh
36
+ register: gem_patch_report_result
37
+ become: true
38
+ when: send_stats == true and stats_url is defined and stats_api_key is defined
39
+ tags:
40
+ - maintenance
41
+ - stats
42
+ - gem-patch-report
43
+ ignore_errors: yes
44
+
45
+ - name: Read gem patch report file
46
+ slurp:
47
+ src: /tmp/gem-patch-report.json
48
+ register: gem_patch_report_file
49
+ when: send_stats == true and stats_url is defined and stats_api_key is defined
50
+ tags:
51
+ - maintenance
52
+ - stats
53
+ - gem-patch-report
54
+ ignore_errors: yes
55
+
56
+ - name: Send gem patch report to stats server
57
+ uri:
58
+ url: "{{ stats_url }}"
59
+ method: POST
60
+ headers:
61
+ X-API-Version: 1
62
+ X-Client-Api-key: "{{ stats_api_key }}"
63
+ body_format: json
64
+ body:
65
+ client_stat:
66
+ stat_type: gem_patch_report
67
+ value: "{{ (gem_patch_report_file.content | b64decode) | from_json }}"
68
+ hostname: "{{ hostname }}"
69
+ when: send_stats == true and stats_url is defined and stats_api_key is defined and gem_patch_report_file is defined and gem_patch_report_file is not failed
70
+ tags:
71
+ - maintenance
72
+ - stats
73
+ - gem-patch-report
74
+ ignore_errors: yes
75
+
76
+ - name: Clean up temporary gem patch report files
77
+ file:
78
+ path: "{{ item }}"
79
+ state: absent
80
+ become: true
81
+ loop:
82
+ - /tmp/gem-patch-report.sh
83
+ - /tmp/gem-patch-report.json
84
+ when: send_stats == true and stats_url is defined and stats_api_key is defined
85
+ tags:
86
+ - maintenance
87
+ - stats
88
+ - gem-patch-report
89
+ ignore_errors: yes
@@ -0,0 +1,114 @@
1
+ #!/bin/bash
2
+
3
+ APP_PATH="{{ gem_patch_report_app_path }}"
4
+ DEPLOY_DIR=$(cd "$APP_PATH" && cd .. && pwd)
5
+ REPO_DIR="$DEPLOY_DIR/repo"
6
+ TEMP_DIR="/tmp/gem-patch-report-$$"
7
+ REPORT_FILE="$TEMP_DIR/report.json"
8
+ OLD_AUDIT_FILE="$TEMP_DIR/audit_old.json"
9
+ CURRENT_AUDIT_FILE="$TEMP_DIR/audit_current.json"
10
+ OLD_GEMFILE_LOCK="$APP_PATH/Gemfile.lock.old"
11
+
12
+ # Create temp directory
13
+ mkdir -p "$TEMP_DIR"
14
+
15
+ # Check if current Gemfile.lock exists
16
+ if [ ! -f "$APP_PATH/Gemfile.lock" ]; then
17
+ echo '[]' > /tmp/gem-patch-report.json
18
+ rm -rf "$TEMP_DIR"
19
+ exit 0
20
+ fi
21
+
22
+ # Update bundle-audit advisory database
23
+ cd "$APP_PATH"
24
+ bundle exec bundle-audit update 2>/dev/null || true
25
+
26
+ # Get old Gemfile.lock from the beginning of the month
27
+ FIRST_OF_MONTH=$(date +"%Y-%m-01")
28
+ OLD_COMMIT=$(git -c safe.directory="$REPO_DIR" --git-dir="$REPO_DIR" log --all --before="$FIRST_OF_MONTH" -1 --format="%H" -- Gemfile.lock 2>/dev/null)
29
+
30
+ if [ -z "$OLD_COMMIT" ]; then
31
+ # No commit before this month, nothing to compare
32
+ echo '[]' > /tmp/gem-patch-report.json
33
+ rm -rf "$TEMP_DIR"
34
+ exit 0
35
+ fi
36
+
37
+ git -c safe.directory="$REPO_DIR" --git-dir="$REPO_DIR" show "${OLD_COMMIT}:Gemfile.lock" > "$OLD_GEMFILE_LOCK"
38
+
39
+ # Run bundle-audit on the old Gemfile.lock
40
+ # bundle-audit exits non-zero when vulnerabilities are found, so ignore the exit code
41
+ bundle exec bundle-audit check --gemfile-lock Gemfile.lock.old --format json > "$OLD_AUDIT_FILE" 2>/dev/null || true
42
+
43
+ # Run bundle-audit on current Gemfile.lock
44
+ bundle exec bundle-audit check --format json > "$CURRENT_AUDIT_FILE" 2>/dev/null || true
45
+
46
+ # Set environment variables for Ruby script
47
+ export OLD_AUDIT_FILE="$OLD_AUDIT_FILE"
48
+ export CURRENT_AUDIT_FILE="$CURRENT_AUDIT_FILE"
49
+ export CURRENT_GEMFILE_LOCK="$APP_PATH/Gemfile.lock"
50
+
51
+ # Generate patch report using Ruby
52
+ ruby << 'RUBY' > "$REPORT_FILE"
53
+ require 'json'
54
+ require 'set'
55
+
56
+ begin
57
+ old_audit = JSON.parse(File.read(ENV['OLD_AUDIT_FILE']))
58
+ current_audit = JSON.parse(File.read(ENV['CURRENT_AUDIT_FILE']))
59
+
60
+ old_results = old_audit['results'] || []
61
+ current_results = current_audit['results'] || []
62
+
63
+ # Build a set of current vulnerability keys (gem name + advisory id)
64
+ current_vuln_keys = current_results
65
+ .select { |r| r['type'] == 'unpatched_gem' && r['gem'] && r['advisory'] }
66
+ .map { |r| "#{r['gem']['name']}:#{r['advisory']['id']}" }
67
+ .to_set
68
+
69
+ # Parse current Gemfile.lock to get current gem versions
70
+ current_versions = {}
71
+ gemfile_lock = File.read(ENV['CURRENT_GEMFILE_LOCK']) rescue ''
72
+ in_specs = false
73
+ gemfile_lock.each_line do |line|
74
+ if line.strip == 'specs:'
75
+ in_specs = true
76
+ next
77
+ end
78
+ if in_specs
79
+ if line =~ /^\s{4}(\S+)\s+\(([^)]+)\)/
80
+ current_versions[$1] = $2
81
+ elsif line !~ /^\s/
82
+ in_specs = false
83
+ end
84
+ end
85
+ end
86
+
87
+ # Only include gems that were vulnerable at start of month but are now patched
88
+ patched_gems = old_results
89
+ .select { |r| r['type'] == 'unpatched_gem' && r['gem'] && r['advisory'] }
90
+ .reject { |r| current_vuln_keys.include?("#{r['gem']['name']}:#{r['advisory']['id']}") }
91
+ .map { |r|
92
+ {
93
+ 'name' => r['gem']['name'],
94
+ 'version' => r['gem']['version'],
95
+ 'current_version' => current_versions[r['gem']['name']] || 'unknown',
96
+ 'advisory_id' => r['advisory']['id'],
97
+ 'criticality' => r['advisory']['criticality']
98
+ }
99
+ }
100
+ .sort_by { |g| [g['name'], g['advisory_id']] }
101
+
102
+ puts JSON.pretty_generate(patched_gems)
103
+
104
+ rescue JSON::ParserError => e
105
+ puts '[]'
106
+ rescue => e
107
+ puts '[]'
108
+ end
109
+ RUBY
110
+
111
+ # Copy report to known location and clean up
112
+ cp "$REPORT_FILE" /tmp/gem-patch-report.json
113
+ rm -rf "$TEMP_DIR"
114
+ rm -f "$OLD_GEMFILE_LOCK"
@@ -1,17 +1,64 @@
1
- - name: Install nginx
2
- apt: pkg=nginx state=latest
1
+ - name: Remove ondrej nginx packages before switching to official repo
2
+ apt:
3
+ pkg:
4
+ - nginx
5
+ - nginx-common
6
+ - nginx-core
7
+ - nginx-full
8
+ state: absent
9
+ purge: no
3
10
  become: true
4
11
 
5
- - name: Disable Server tokens
6
- lineinfile:
7
- path: /etc/nginx/nginx.conf
8
- regexp: '# server_tokens off'
9
- line: "\tserver_tokens off;"
12
+ - name: Install nginx from official repo
13
+ apt:
14
+ pkg: nginx
15
+ state: latest
16
+ update_cache: yes
17
+ become: true
10
18
 
11
- - name: Remove the default app
19
+ - name: Remove default nginx config files
12
20
  file:
13
- path: /etc/nginx/sites-enabled/default
21
+ path: "{{ item }}"
14
22
  state: absent
23
+ loop:
24
+ - /etc/nginx/sites-enabled/default
25
+ - /etc/nginx/conf.d/default.conf
26
+ become: true
27
+
28
+ - name: Ensure sites-available directory exists
29
+ file:
30
+ path: /etc/nginx/sites-available
31
+ state: directory
32
+ owner: root
33
+ group: root
34
+ mode: '0755'
35
+ become: true
36
+
37
+ - name: Ensure sites-enabled directory exists
38
+ file:
39
+ path: /etc/nginx/sites-enabled
40
+ state: directory
41
+ owner: root
42
+ group: root
43
+ mode: '0755'
44
+ become: true
45
+
46
+ - name: Ensure modules-enabled directory exists
47
+ file:
48
+ path: /etc/nginx/modules-enabled
49
+ state: directory
50
+ owner: root
51
+ group: root
52
+ mode: '0755'
53
+ become: true
54
+
55
+ - name: Deploy nginx.conf
56
+ template:
57
+ src: nginx.conf
58
+ dest: /etc/nginx/nginx.conf
59
+ owner: root
60
+ group: root
61
+ mode: '0644'
15
62
  become: true
16
63
 
17
64
  - name: "Configure rails projects"
@@ -0,0 +1,29 @@
1
+ user www-data;
2
+ worker_processes auto;
3
+ pid /run/nginx.pid;
4
+ error_log /var/log/nginx/error.log;
5
+ include /etc/nginx/modules-enabled/*.conf;
6
+
7
+ events {
8
+ worker_connections 768;
9
+ }
10
+
11
+ http {
12
+ sendfile on;
13
+ tcp_nopush on;
14
+ types_hash_max_size 2048;
15
+ server_tokens off;
16
+
17
+ include /etc/nginx/mime.types;
18
+ default_type application/octet-stream;
19
+
20
+ ssl_protocols TLSv1.2 TLSv1.3;
21
+ ssl_prefer_server_ciphers on;
22
+
23
+ access_log /var/log/nginx/access.log;
24
+
25
+ gzip on;
26
+
27
+ include /etc/nginx/conf.d/*.conf;
28
+ include /etc/nginx/sites-enabled/*;
29
+ }
@@ -1,3 +1,3 @@
1
1
  module Subspace
2
- VERSION = "3.0.20"
2
+ VERSION = "3.0.22"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: subspace
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.20
4
+ version: 3.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Samson
@@ -153,6 +153,10 @@ files:
153
153
  - ansible/roles/delayed_job/tasks/main.yml
154
154
  - ansible/roles/delayed_job/templates/delayed-job-monit-rc
155
155
  - ansible/roles/delayed_job/templates/delayed-job-systemd.service
156
+ - ansible/roles/gem-patch-report/README.md
157
+ - ansible/roles/gem-patch-report/defaults/main.yml
158
+ - ansible/roles/gem-patch-report/tasks/main.yml
159
+ - ansible/roles/gem-patch-report/templates/gem-patch-report.sh.j2
156
160
  - ansible/roles/letsencrypt/defaults/main.yml
157
161
  - ansible/roles/letsencrypt/tasks/legacy.yml
158
162
  - ansible/roles/letsencrypt/tasks/main.yml
@@ -188,6 +192,7 @@ files:
188
192
  - ansible/roles/nginx/defaults/main.yml
189
193
  - ansible/roles/nginx/handlers/main.yml
190
194
  - ansible/roles/nginx/tasks/main.yml
195
+ - ansible/roles/nginx/templates/nginx.conf
191
196
  - ansible/roles/nginx/templates/status
192
197
  - ansible/roles/nodejs/tasks/main.yml
193
198
  - ansible/roles/papertrail/tasks/main.yml
@@ -326,7 +331,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
326
331
  - !ruby/object:Gem::Version
327
332
  version: '0'
328
333
  requirements: []
329
- rubygems_version: 4.0.8
334
+ rubygems_version: 4.0.10
330
335
  specification_version: 4
331
336
  summary: Ansible-based server provisioning for rails projects
332
337
  test_files: []