Merge ~hloeung/content-cache-charm:master into content-cache-charm:master

Proposed by Haw Loeung
Status: Merged
Approved by: Haw Loeung
Approved revision: 02cbeabb9a21fafab378ce4c7c6a501ca1a8bc17
Merged at revision: f6ed3c5572f0ac798390362e681ced5a31da6094
Proposed branch: ~hloeung/content-cache-charm:master
Merge into: content-cache-charm:master
Diff against target: 181 lines (+119/-7)
5 files modified
config.yaml (+11/-2)
reactive/content_cache.py (+30/-5)
tests/unit/files/config_test_secrets.txt (+2/-0)
tests/unit/files/nginx_config_rendered_test_output-site1.local-secrets.txt (+22/-0)
tests/unit/test_content_cache.py (+54/-0)
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+365077@code.launchpad.net

Commit message

Allow overriding secrets of origin headers with a dict

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Stuart Bishop (stub) wrote :

Looks good. Should be good to land with modifications, or hit me up for a re-review if you think it is necessary.

review: Approve
Revision history for this message
Haw Loeung (hloeung) :
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision f6ed3c5572f0ac798390362e681ced5a31da6094

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/config.yaml b/config.yaml
2index 2fa03a3..3e949e6 100644
3--- a/config.yaml
4+++ b/config.yaml
5@@ -18,5 +18,14 @@ options:
6 YAML-formatted virtual hosts/sites. e.g.
7 site1.local:
8 backends:
9- - 91.189.88.149:80
10- - 91.189.88.152:80
11+ - 91.189.88.149:80
12+ - 91.189.88.152:80
13+ origin-headers:
14+ - X-Origin-Key: ${secret}
15+ sites_secrets:
16+ default: ""
17+ type: string
18+ description: |
19+ YAML-formatted dictionary of secrets/keys. e.g.
20+ site1.local:
21+ X-Origin-Key: mysecretkey
22diff --git a/reactive/content_cache.py b/reactive/content_cache.py
23index 51da197..b7c1663 100644
24--- a/reactive/content_cache.py
25+++ b/reactive/content_cache.py
26@@ -70,16 +70,18 @@ def configure_nginx():
27 status.blocked('list of sites provided has no backends or seems invalid')
28 reactive.clear_flag('content_cache.active')
29 return
30+ sites_secrets = secrets_from_config(config.get('sites_secrets'))
31
32 changed = False
33- for site in sites.keys():
34- cache_port = sites[site]['cache_port']
35- backend_port = sites[site]['backend_port']
36+ for site, site_conf in sites.items():
37+ cache_port = site_conf['cache_port']
38+ backend_port = site_conf['backend_port']
39 backend = 'http://localhost:{}'.format(backend_port)
40 # Per site secret HMAC key, if it exists. We pass this through to the
41 # caching layer to activate the bit to restrict access.
42- signed_url_hmac_key = sites[site].get('signed-url-hmac-key')
43- origin_headers = sites[site].get('origin-headers')
44+ signed_url_hmac_key = site_conf.get('signed-url-hmac-key')
45+ secrets = sites_secrets.get(site)
46+ origin_headers = map_origin_headers_to_secrets(site_conf.get('origin-headers'), secrets)
47 if ngx_conf.write_site(site, ngx_conf.render(site, cache_port, backend, signed_url_hmac_key, origin_headers)):
48 hookenv.log('Wrote out new configs for site: {}'.format(site))
49 changed = True
50@@ -215,3 +217,26 @@ def sites_from_config(sites_yaml):
51 site_conf['cache_port'] = cache_port
52 site_conf['backend_port'] = backend_port
53 return conf
54+
55+
56+def secrets_from_config(secrets_yaml):
57+ secrets = ''
58+ if not secrets_yaml:
59+ return {}
60+ try:
61+ secrets = yaml.safe_load(secrets_yaml)
62+ except yaml.YAMLError:
63+ return {}
64+ if isinstance(secrets, dict):
65+ return secrets
66+ else:
67+ return {}
68+
69+
70+def map_origin_headers_to_secrets(origin_headers, secrets):
71+ if origin_headers:
72+ for l in origin_headers:
73+ for header, value in l.items():
74+ if value == '${secret}':
75+ l[header] = secrets.get(header)
76+ return origin_headers
77diff --git a/tests/unit/files/config_test_secrets.txt b/tests/unit/files/config_test_secrets.txt
78new file mode 100644
79index 0000000..7210a90
80--- /dev/null
81+++ b/tests/unit/files/config_test_secrets.txt
82@@ -0,0 +1,2 @@
83+site1.local:
84+ X-Origin-Key: Sae6oob2aethuosh
85diff --git a/tests/unit/files/nginx_config_rendered_test_output-site1.local-secrets.txt b/tests/unit/files/nginx_config_rendered_test_output-site1.local-secrets.txt
86new file mode 100644
87index 0000000..48089e2
88--- /dev/null
89+++ b/tests/unit/files/nginx_config_rendered_test_output-site1.local-secrets.txt
90@@ -0,0 +1,22 @@
91+proxy_cache_path /var/lib/nginx/proxy/site1.local use_temp_path=off levels=1:2 keys_zone=site1-cache:10m max_size=1g;
92+
93+server {
94+ server_name site1.local;
95+ listen 6080;
96+
97+ location / {
98+ proxy_pass http://localhost:8080;
99+ proxy_set_header Host "site1.local";
100+ proxy_cache site1-cache;
101+ proxy_cache_background_update on;
102+ proxy_cache_lock on;
103+ proxy_cache_min_uses 5;
104+ proxy_cache_revalidate on;
105+ proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
106+ proxy_cache_valid 200 1d;
107+ proxy_set_header X-Origin-Key Sae6oob2aethuosh;
108+ }
109+
110+ access_log /var/log/nginx/site1.local-access.log;
111+ error_log /var/log/nginx/site1.local-error.log;
112+}
113diff --git a/tests/unit/test_content_cache.py b/tests/unit/test_content_cache.py
114index db15a60..596d4b3 100644
115--- a/tests/unit/test_content_cache.py
116+++ b/tests/unit/test_content_cache.py
117@@ -151,6 +151,39 @@ class TestCharm(unittest.TestCase):
118 current = f.read()
119 self.assertEqual(expected, current)
120
121+ @mock.patch('reactive.content_cache.service_start_or_restart')
122+ def test_configure_nginx_sites_secrets(self, service_start_or_restart):
123+ with open('tests/unit/files/config_test_secrets.txt', 'r', encoding='utf-8') as f:
124+ secrets = f.read()
125+ config = '''
126+site1.local:
127+ backends:
128+ - 127.0.1.10:80
129+ - 127.0.1.11:80
130+ - 127.0.1.12:80
131+ origin-headers:
132+ - X-Origin-Key: ${secret}
133+'''
134+ self.mock_config.return_value = {
135+ 'sites': config,
136+ 'sites_secrets': secrets,
137+ }
138+
139+ with mock.patch('lib.nginx.NginxConf.sites_path', new_callable=mock.PropertyMock) as mock_site_path:
140+ mock_site_path.return_value = os.path.join(self.tmpdir, 'sites-available')
141+ # sites-available and sites-enabled won't exist in our temp dir
142+ os.mkdir(os.path.join(self.tmpdir, 'sites-available'))
143+ os.mkdir(os.path.join(self.tmpdir, 'sites-enabled'))
144+ content_cache.configure_nginx()
145+ for site in ['site1.local']:
146+ with open('tests/unit/files/nginx_config_rendered_test_output-{}-secrets.txt'.format(site),
147+ 'r', encoding='utf-8') as f:
148+ expected = f.read()
149+ with open(os.path.join(self.tmpdir, 'sites-available/{}.conf'.format(site)),
150+ 'r', encoding='utf-8') as f:
151+ current = f.read()
152+ self.assertEqual(expected, current)
153+
154 @mock.patch('charms.reactive.clear_flag')
155 @mock.patch('charms.reactive.set_flag')
156 def test_configure_nginx_sites_no_backend(self, set_flag, clear_flag):
157@@ -297,3 +330,24 @@ site1.local:
158 port: 80
159 '''
160 self.assertFalse(content_cache.sites_from_config(config_yaml))
161+
162+ def test_secrets_from_config(self):
163+ secrets_yaml = '''
164+site1.local:
165+ X-Some-Header: myvalue
166+'''
167+ expected = {
168+ 'site1.local': {
169+ 'X-Some-Header': 'myvalue',
170+ }
171+ }
172+ self.assertEqual(content_cache.secrets_from_config(secrets_yaml), expected)
173+ self.assertEqual(content_cache.secrets_from_config(''), {})
174+ self.assertEqual(content_cache.secrets_from_config('invalid YAML'), {})
175+ self.assertEqual(content_cache.secrets_from_config('invalid\n\tYAML'), {})
176+
177+ def test_map_origin_headers_to_secrets(self):
178+ origin_headers = [{'X-Origin-Key': '${secret}'}]
179+ secrets = {'X-Origin-Key': 'Sae6oob2aethuosh'}
180+ expected = [{'X-Origin-Key': 'Sae6oob2aethuosh'}]
181+ self.assertEqual(content_cache.map_origin_headers_to_secrets(origin_headers, secrets), expected)

Subscribers

People subscribed via source and target branches