Skip to content

Commit 577ffde

Browse files
authored
Merge pull request #16 from rhythmictech/NOC-49582
add support for optional web.xml parameters, stamp it as it changes b…
2 parents abc9333 + b8522b2 commit 577ffde

6 files changed

Lines changed: 211 additions & 12 deletions

File tree

.github/workflows/test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ jobs:
77
runs-on: ubuntu-latest
88
steps:
99
- name: checkout
10-
uses: actions/checkout@v2
10+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
1111
- name: molecule
12-
uses: robertdebock/molecule-action@2.0.0
12+
uses: robertdebock/molecule-action@d8c1b1a1b5b223f2c102983aa0e9402daa4d8e4b # 2.0.0
1313
with:
1414
entrypoint: /usr/local/bin/molecule
1515
args: lint
@@ -31,12 +31,12 @@ jobs:
3131
tag: "2"
3232
steps:
3333
- name: checkout
34-
uses: actions/checkout@v2
34+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
3535
with:
3636
path: "${{ github.repository }}"
3737
- name: molecule
38-
uses: robertdebock/molecule-action@2.0.0
38+
uses: robertdebock/molecule-action@d8c1b1a1b5b223f2c102983aa0e9402daa4d8e4b # 2.0.0
3939
with:
4040
image: ${{ matrix.config.image }}
4141
tag: ${{ matrix.config.tag }}
42-
options: "--parallel all"
42+
options: "--parallel all"

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ Requirements
88

99
This role requires Ansible 2.8 or higher.
1010

11+
This role requires the `community.general` collection, which provides the `xml`
12+
module used to apply the secure session cookie settings to `web.xml`. The
13+
`lxml` python library is installed on the target host by the role itself.
14+
1115
This role was designed for CentOS 6.x, 7.x and 8,x, as well as Amazon Linux 2.
1216

1317
Role Variables

defaults/main.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ tomcat_packages:
2323
- "tomcat{{ tomcat_major_version }}"
2424

2525
tomcat_secure_http_headers: true
26+
tomcat_secure_session_cookie: true
27+
28+
# Refresh CATALINA_BASE/conf/web.xml from the installed distribution's stock
29+
# descriptor whenever that descriptor changes (i.e. on a Tomcat upgrade).
30+
# Without this, conf/web.xml keeps whatever base it had when the host was first
31+
# provisioned, since the conf.dist copy only runs on initial provisioning.
32+
tomcat_manage_webxml_baseline: true
2633

2734
tomcat_connection_timeout: 20000
2835

@@ -82,6 +89,18 @@ tomcat_shutdown_wait: 20
8289
tomcat_start_service: false
8390

8491
tomcat_contexts: []
92+
# Additional servlet filters to declare in CATALINA_BASE/conf/web.xml. Only
93+
# `name` and `class` are required. Emptying the list removes the managed block.
94+
# The filter class must already be on the shared classpath -- conf/web.xml is
95+
# the global descriptor, so an unloadable class fails every context on startup.
96+
# - name: myFilter
97+
# class: com.example.MyFilter
98+
# url_pattern: '/*' # default '/*'
99+
# dispatchers: ['REQUEST'] # default ['REQUEST']
100+
# init_params:
101+
# - name: someOption
102+
# value: someValue
103+
tomcat_extra_filters: []
85104
tomcat_extra_loggers: []
86105
tomcat_global_context:
87106
jdbc_resources: []

molecule/default/collections.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
collections:
3+
# Required by the role itself (community.general.xml drives the web.xml
4+
# session-config edits).
5+
- name: community.general
6+
version: ">=1.0.0"
7+
# Required by the molecule docker driver.
8+
- name: community.docker
9+
version: ">=3.10.2"
10+
- name: ansible.posix
11+
version: ">=1.4.0"

requirements.txt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
molecule==3.0.2
2-
ansible==2.8.8
3-
ansible-lint==4.1.0
4-
docker==4.0.2
5-
boto3==1.9.212
1+
# Local development / test toolchain. These are the versions the molecule
2+
# default scenario is verified against; the GitHub Actions lint and test jobs
3+
# run molecule from the robertdebock/molecule-action image instead.
4+
#
5+
# Collections are declared separately in molecule/default/collections.yml.
6+
ansible-core==2.21.2
7+
ansible-lint==26.6.0
8+
docker==7.2.0
9+
molecule==26.6.0
10+
molecule-plugins[docker]==26.7.15
11+
yamllint==1.38.0

tasks/main.yml

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,27 @@
2727
when: tomcat_add_user_to_group
2828
tags: ['tomcat']
2929

30-
- set_fact:
30+
# Without this, the set_fact below mints a new random value on every run, so
31+
# server.xml is rewritten every time (and the on-disk value drifts from the
32+
# running one, since nothing restarts tomcat for a server.xml change).
33+
- name: read any existing tomcat shutdown value
34+
ansible.builtin.command:
35+
cmd: sed -n 's/.*<Server[^>]*shutdown="\([^"]*\)".*/\1/p' {{ _tomcat_catalina_base }}/conf/server.xml
36+
register: tomcat_shutdown_existing
37+
changed_when: false
38+
failed_when: false
39+
check_mode: false
40+
tags: ['tomcat']
41+
42+
- name: preserve the existing tomcat shutdown value
43+
ansible.builtin.set_fact:
44+
tomcat_shutdown_value: "{{ tomcat_shutdown_existing.stdout_lines | first }}"
45+
when:
46+
- tomcat_shutdown_value == "SETME"
47+
- tomcat_shutdown_existing.stdout_lines | default([]) | length > 0
48+
tags: ['tomcat']
49+
50+
- set_fact:
3151
tomcat_shutdown_value: "{{ lookup('password', '/dev/null length=20 chars=ascii_letters') }}"
3252
when: tomcat_shutdown_value == "SETME"
3353
tags: ['tomcat']
@@ -68,6 +88,70 @@
6888
when: tomcat_conf_found.stat.exists == false
6989
tags: ['tomcat']
7090

91+
# The conf.dist copy above only runs on initial provisioning, so conf/web.xml
92+
# would otherwise keep its original base forever and drift from the installed
93+
# Tomcat version. Stamping with conf.dist's checksum (rather than
94+
# tomcat_version) keeps this correct even if the stamp and the installed
95+
# distribution get out of step. Skipped for rpm installs, which have no
96+
# conf.dist.
97+
- name: check for the tomcat distribution's stock web.xml
98+
ansible.builtin.stat:
99+
path: "{{ _tomcat_catalina_home }}/conf.dist/web.xml"
100+
checksum_algorithm: sha1
101+
register: tomcat_webxml_dist
102+
when: tomcat_manage_webxml_baseline
103+
tags: ['tomcat']
104+
105+
- name: check whether catalina base web.xml is present
106+
ansible.builtin.stat:
107+
path: "{{ _tomcat_catalina_base }}/conf/web.xml"
108+
register: tomcat_webxml_current
109+
when: tomcat_manage_webxml_baseline
110+
tags: ['tomcat']
111+
112+
- name: check the web.xml baseline stamp
113+
ansible.builtin.command:
114+
cmd: grep -q "ANSIBLE MANAGED WEBXML BASELINE {{ tomcat_webxml_dist.stat.checksum }}" {{ _tomcat_catalina_base }}/conf/web.xml
115+
register: tomcat_webxml_stamp
116+
changed_when: false
117+
failed_when: false
118+
check_mode: false
119+
when:
120+
- tomcat_manage_webxml_baseline
121+
- tomcat_webxml_dist.stat.exists | default(false)
122+
- tomcat_webxml_current.stat.exists | default(false)
123+
tags: ['tomcat']
124+
125+
- name: refresh web.xml from the tomcat distribution
126+
ansible.builtin.copy:
127+
src: "{{ _tomcat_catalina_home }}/conf.dist/web.xml"
128+
remote_src: true
129+
dest: "{{ _tomcat_catalina_base }}/conf/web.xml"
130+
owner: "{{ tomcat_user }}"
131+
group: "{{ tomcat_group }}"
132+
mode: '0640'
133+
when:
134+
- tomcat_manage_webxml_baseline
135+
- tomcat_webxml_dist.stat.exists | default(false)
136+
- tomcat_webxml_current.stat.exists | default(false)
137+
- tomcat_webxml_stamp.rc | default(1) != 0
138+
tags: ['tomcat']
139+
140+
- name: stamp web.xml with its baseline checksum
141+
ansible.builtin.lineinfile:
142+
path: "{{ _tomcat_catalina_base }}/conf/web.xml"
143+
insertbefore: "</web-app>"
144+
line: "<!-- ANSIBLE MANAGED WEBXML BASELINE {{ tomcat_webxml_dist.stat.checksum }} -->"
145+
owner: "{{ tomcat_user }}"
146+
group: "{{ tomcat_group }}"
147+
mode: '0640'
148+
when:
149+
- tomcat_manage_webxml_baseline
150+
- tomcat_webxml_dist.stat.exists | default(false)
151+
- tomcat_webxml_current.stat.exists | default(false)
152+
- tomcat_webxml_stamp.rc | default(1) != 0
153+
tags: ['tomcat']
154+
71155
- name: Add HTTP Header Security Filter to web.xml
72156
ansible.builtin.blockinfile:
73157
path: "{{ _tomcat_catalina_base }}/conf/web.xml"
@@ -90,7 +174,82 @@
90174
when: tomcat_secure_http_headers
91175
tags: ['tomcat']
92176

93-
- name: secure catalina base config files
177+
# Declared after the httpHeaderSecurity block so that filter keeps precedence in
178+
# the chain. `state` is driven by the list so emptying it removes the block
179+
# rather than orphaning it. Registered (rather than notifying a handler) so
180+
# consumers can decide whether a web.xml change warrants a restart.
181+
- name: manage extra web.xml filters
182+
ansible.builtin.blockinfile:
183+
path: "{{ _tomcat_catalina_base }}/conf/web.xml"
184+
insertbefore: "</web-app>"
185+
marker: "<!-- {mark} ANSIBLE MANAGED EXTRA FILTERS -->"
186+
state: "{{ 'present' if tomcat_extra_filters else 'absent' }}"
187+
owner: "{{ tomcat_user }}"
188+
group: "{{ tomcat_group }}"
189+
mode: '0640'
190+
block: |
191+
{% for f in tomcat_extra_filters %}
192+
<filter>
193+
<filter-name>{{ f.name }}</filter-name>
194+
<filter-class>{{ f.class }}</filter-class>
195+
{% for p in f.init_params | default([]) %}
196+
<init-param>
197+
<param-name>{{ p.name }}</param-name>
198+
<param-value>{{ p.value }}</param-value>
199+
</init-param>
200+
{% endfor %}
201+
</filter>
202+
<filter-mapping>
203+
<filter-name>{{ f.name }}</filter-name>
204+
<url-pattern>{{ f.url_pattern | default('/*') }}</url-pattern>
205+
{% for d in f.dispatchers | default(['REQUEST']) %}
206+
<dispatcher>{{ d }}</dispatcher>
207+
{% endfor %}
208+
</filter-mapping>
209+
{% endfor %}
210+
register: tomcat_extra_filters_webxml
211+
tags: ['tomcat']
212+
213+
# community.general.xml runs on the target and needs lxml there. Package name
214+
# differs because CentOS 6/7 hosts run ansible under python2.
215+
- name: ensure lxml is available for the web.xml session-config edits
216+
ansible.builtin.package:
217+
name: "{{ 'python3-lxml' if ansible_python_version is version('3', '>=') else 'python-lxml' }}"
218+
state: present
219+
when: tomcat_secure_session_cookie
220+
tags: ['tomcat']
221+
222+
- name: Ensure cookie-config element exists in session-config
223+
community.general.xml:
224+
path: "{{ _tomcat_catalina_base }}/conf/web.xml"
225+
xpath: /javaee:web-app/javaee:session-config/javaee:cookie-config
226+
namespaces:
227+
javaee: http://xmlns.jcp.org/xml/ns/javaee
228+
state: present
229+
when: tomcat_secure_session_cookie
230+
tags: ['tomcat']
231+
232+
- name: Set http-only flag on session cookie
233+
community.general.xml:
234+
path: "{{ _tomcat_catalina_base }}/conf/web.xml"
235+
xpath: /javaee:web-app/javaee:session-config/javaee:cookie-config/javaee:http-only
236+
namespaces:
237+
javaee: http://xmlns.jcp.org/xml/ns/javaee
238+
value: "true"
239+
when: tomcat_secure_session_cookie
240+
tags: ['tomcat']
241+
242+
- name: Set secure flag on session cookie
243+
community.general.xml:
244+
path: "{{ _tomcat_catalina_base }}/conf/web.xml"
245+
xpath: /javaee:web-app/javaee:session-config/javaee:cookie-config/javaee:secure
246+
namespaces:
247+
javaee: http://xmlns.jcp.org/xml/ns/javaee
248+
value: "true"
249+
when: tomcat_secure_session_cookie
250+
tags: ['tomcat']
251+
252+
- name: secure catalina base config files
94253
file:
95254
path: '{{ _tomcat_catalina_base }}/conf/{{ item }}'
96255
owner: '{{ tomcat_user }}'

0 commit comments

Comments
 (0)