Merge ~peter-sabaini/charm-sudo-pair:feature/add-pd-logging into charm-sudo-pair:master

Proposed by Peter Sabaini
Status: Rejected
Rejected by: Eric Chen
Proposed branch: ~peter-sabaini/charm-sudo-pair:feature/add-pd-logging
Merge into: charm-sudo-pair:master
Diff against target: 297 lines (+143/-6)
10 files modified
src/actions/actions.py (+13/-0)
src/config.yaml (+8/-0)
src/files/pagerdutyevent.py (+68/-0)
src/lib/libsudopair.py (+33/-1)
src/reactive/sudo_pair.py (+4/-0)
src/templates/sudo_approve.tmpl (+5/-2)
src/templates/sudo_pair.pagerduty.tmpl (+6/-0)
src/tests/unit/conftest.py (+2/-1)
src/tests/unit/test_actions.py (+3/-1)
src/tests/unit/test_libsudopair.py (+1/-1)
Reviewer Review Type Date Requested Status
James Troup (community) Needs Fixing
Paul Goins Approve
Tom Haddon Abstain
BootStack Reviewers mr tracking; do not claim Pending
Giuseppe Petralia Pending
Review via email: mp+403957@code.launchpad.net

This proposal supersedes a proposal from 2021-06-09.

Commit message

Pagerduty alerting, action logging

Ability to have pagerduty alerts triggered on auto-approve. Revamp logging, add logging for remove-sudopair action. Use https proxy to contact the PD events endpoint if set in model config.

To post a comment you must log in.
Revision history for this message
Giuseppe Petralia (peppepetra) wrote : Posted in a previous version of this proposal

Small comment on the pagerduty_proxy that may be removed in favor of the juju model-config if any.

Comments in line.

Other than that looks good to me.

review: Needs Fixing
Revision history for this message
Paul Goins (vultaire) wrote : Posted in a previous version of this proposal

I agree with Giuseppe. I think we should pull the proxy from the env's JUJU_CHARM_HTTPS_PROXY variable to avoid proliferating proxy settings. Other than that I'm +1.

review: Needs Fixing
Revision history for this message
Peter Sabaini (peter-sabaini) wrote : Posted in a previous version of this proposal

Thanks -- proxy config updated as requested. I've resubmitted the branch as there was some code reorg interim.

Revision history for this message
Celia Wang (ziyiwang) wrote : Posted in a previous version of this proposal

LGTM.

Revision history for this message
Paul Goins (vultaire) wrote : Posted in a previous version of this proposal

Sorry for the delay here - It looks like this is close to if not identical to https://code.launchpad.net/~peter-sabaini/charm-sudo-pair/+git/sudo-pair-charm/+merge/377884, except that it might be based off newer code. I don't see any changes re: proxy variable use; this code is still using pagerduty_proxy rather than e.g. JUJU_CHARM_HTTPS_PROXY.

review: Needs Fixing
Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Apologies, appears I superseded with the wrong branch here. Trying this again

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
Tom Haddon (mthaddon) wrote :

Abstaining, just claimed the review to take it off the canonical-is-reviewers dashboard, mergebot has been switched to add bootstack-reviewers for future MPs.

review: Abstain
Revision history for this message
Paul Goins (vultaire) wrote :

Looks good to me!

review: Approve
Revision history for this message
James Troup (elmo) wrote :

Couple of comments inline; I consider the config file permissions blocking unless I'm wrong about the perms?

review: Needs Fixing
7b10f8a... by Peter Sabaini

Address review comments

Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Should be better now, please take a look

Unmerged commits

7b10f8a... by Peter Sabaini

Address review comments

3681b10... by Peter Sabaini

Pagerduty alerting, action logging

Ability to have pagerduty alerts triggered on auto-approve. Revamp
logging, add logging for remove-sudopair action. Use https proxy to
contact the PD events endpoint if set in model config.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/actions/actions.py b/src/actions/actions.py
2index 9ff1ec5..65c5a13 100755
3--- a/src/actions/actions.py
4+++ b/src/actions/actions.py
5@@ -14,7 +14,10 @@
6 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7 # See the License for the specific language governing permissions and
8 # limitations under the License.
9+import logging
10+import logging.handlers
11 import os
12+import subprocess
13 import sys
14
15 from charmhelpers.core.hookenv import action_fail, action_set, status_set
16@@ -25,11 +28,21 @@ sys.path.append("lib")
17 import libsudopair # NOQA
18
19
20+logger = logging.getLogger("sudopair")
21+logger.setLevel(logging.INFO)
22+handler = logging.handlers.SysLogHandler(
23+ address="/dev/log", facility=logging.handlers.SysLogHandler.LOG_AUTH
24+)
25+logger.addHandler(handler)
26+
27+
28 def remove():
29 """Remove sudo-pair config and binaries."""
30 sph = libsudopair.SudoPairHelper()
31 sph.deconfigure()
32 msg = "Successfully removed sudo-pair config and binaries"
33+ logger.warning(msg)
34+ subprocess.run(["/usr/local/bin/pagerdutyevent", msg])
35 action_set({"message": msg})
36 status_set("active", msg)
37
38diff --git a/src/config.yaml b/src/config.yaml
39index 795269b..6089ac9 100644
40--- a/src/config.yaml
41+++ b/src/config.yaml
42@@ -19,3 +19,11 @@ options:
43 type: boolean
44 default: true
45 description: "If true, auto approval is permitted."
46+ pagerduty_key:
47+ type: string
48+ default: ''
49+ description: "If set, a pagerduty event will be triggered upon auto-approving"
50+ pagerduty_context:
51+ type: string
52+ default: ''
53+ description: "Prefix to add to pagerduty events"
54diff --git a/src/files/pagerdutyevent.py b/src/files/pagerdutyevent.py
55new file mode 100644
56index 0000000..f33be42
57--- /dev/null
58+++ b/src/files/pagerdutyevent.py
59@@ -0,0 +1,68 @@
60+#!/usr/bin/env python3
61+"""Send events to pagerduty."""
62+
63+import argparse
64+import configparser
65+import json
66+import logging
67+import logging.handlers
68+
69+import requests
70+
71+logger = logging.getLogger("sudopair")
72+logger.setLevel(logging.INFO)
73+handler = logging.handlers.SysLogHandler(
74+ address="/dev/log", facility=logging.handlers.SysLogHandler.LOG_AUTH
75+)
76+logger.addHandler(handler)
77+
78+
79+def args():
80+ """Get cli args."""
81+ parser = argparse.ArgumentParser()
82+ parser.add_argument("summary")
83+ return parser.parse_args()
84+
85+
86+def trigger_incident(pd_key, summary, source, https_proxy=None):
87+ """Send an event to the PD events endpoint."""
88+ header = {"Content-Type": "application/json"}
89+
90+ payload = {
91+ "routing_key": pd_key,
92+ "event_action": "trigger",
93+ "payload": {"summary": summary, "source": source, "severity": "info"},
94+ }
95+ if https_proxy:
96+ proxies = {"https": https_proxy}
97+ else:
98+ proxies = None
99+ response = requests.post(
100+ "https://events.pagerduty.com/v2/enqueue",
101+ data=json.dumps(payload),
102+ proxies=proxies,
103+ headers=header,
104+ )
105+
106+ if response.json()["status"] == "success":
107+ logger.info("Triggered alert with key {}".format(response.json()["dedup_key"]))
108+ else:
109+ logger.warning("Failed to send pagerduty alert: {}".format(response.text))
110+
111+
112+def get_conf():
113+ """Return config options from file."""
114+ config = configparser.ConfigParser()
115+ config.read("/etc/sudo_pair.pagerduty.ini")
116+ sec = config["DEFAULT"]
117+ return sec["pagerduty_key"], sec["pagerduty_source"], sec.get("https_proxy")
118+
119+
120+def main():
121+ """Run the script."""
122+ pd_key, source, https_proxy = get_conf()
123+ trigger_incident(pd_key, args().summary, source, https_proxy)
124+
125+
126+if __name__ == "__main__":
127+ main()
128diff --git a/src/lib/libsudopair.py b/src/lib/libsudopair.py
129index 2f7c45b..24dfcf2 100644
130--- a/src/lib/libsudopair.py
131+++ b/src/lib/libsudopair.py
132@@ -67,6 +67,9 @@ class SudoPairHelper(object):
133 self.sudoers_perms = 0o440
134 self.sudo_conf_perms = 0o644
135 self.sudo_approve_perms = 0o755
136+ self.pagerduty_conf_path = "/etc/sudo_pair.pagerduty.ini"
137+ self.pagerduty_path = "/usr/local/bin/pagerdutyevent"
138+ self.pagerduty_perms = 0o755
139
140 def get_config(self):
141 """Return config as a dict."""
142@@ -81,8 +84,11 @@ class SudoPairHelper(object):
143 "gids_exempted": group_names_to_group_ids(
144 self.charm_config["groups_exempted"]
145 ),
146+ "pagerduty_key": self.charm_config.get("pagerduty_key"),
147 }
148-
149+ proxy_settings = hookenv.env_proxy_settings()
150+ if proxy_settings:
151+ config["https_proxy"] = proxy_settings.get("https_proxy")
152 config.update(self.charm_config)
153 return config
154
155@@ -90,6 +96,13 @@ class SudoPairHelper(object):
156 """Update configuration."""
157 self.charm_config = charm_config
158
159+ def copy_pagerduty(self):
160+ """Copy PD script in place."""
161+ pd_file = os.path.join(hookenv.charm_dir(), "files", "pagerdutyevent")
162+ copy_file(
163+ pd_file, self.pagerduty_path, self.owner, self.group, self.pagerduty_perms
164+ )
165+
166 def render_sudo_conf(self):
167 """Render sudo.conf file."""
168 return templating.render(
169@@ -184,6 +197,25 @@ class SudoPairHelper(object):
170 )
171 return None
172
173+ def render_pagerduty_conf(self):
174+ """Create the PD script configuration."""
175+ pd_source = "{}-{}".format(
176+ self.charm_config.get("pagerduty_context", "juju"), hookenv.principal_unit()
177+ )
178+ cfg = self.get_config()
179+ cfg["pagerduty_source"] = pd_source
180+ hookenv.log(
181+ "Rendering pagerduty configuration to {}".format(self.pagerduty_conf_path)
182+ )
183+ return templating.render(
184+ "sudo_pair.pagerduty.tmpl",
185+ self.pagerduty_conf_path,
186+ cfg,
187+ owner=self.owner,
188+ group=self.group,
189+ perms=0o400,
190+ )
191+
192 def deconfigure(self):
193 """Remove sudo-pair configuration."""
194 paths = [
195diff --git a/src/reactive/sudo_pair.py b/src/reactive/sudo_pair.py
196index caab64f..392383b 100644
197--- a/src/reactive/sudo_pair.py
198+++ b/src/reactive/sudo_pair.py
199@@ -32,6 +32,10 @@ def install_sudo_pair():
200 # Add "Defaults log_output to /etc/sudoers
201 sph.copy_sudoers()
202
203+ # Add pagerduty script and config
204+ sph.copy_pagerduty()
205+ sph.render_pagerduty_conf()
206+
207 # If there are cmds to bypass sudo pairing create file unders sudoers.d
208 sph.render_bypass_cmds()
209
210diff --git a/src/templates/sudo_approve.tmpl b/src/templates/sudo_approve.tmpl
211index 7164b5a..5ebace4 100755
212--- a/src/templates/sudo_approve.tmpl
213+++ b/src/templates/sudo_approve.tmpl
214@@ -88,7 +88,7 @@ main() {
215 declare -r username
216
217 declare log_line
218- log_line="$(date "+[%b %d %H:%M:%S] WARNING: ${username} approved is own sudo session.")"
219+ log_line="WARNING: ${username} approved own sudo session."
220 declare -r log_line
221
222 if [[ "${uid}" -eq "${ruid}" ]]; then
223@@ -97,7 +97,10 @@ main() {
224 exit 1
225 {% else %}
226 echo "You are approving your own session. The incident will be logged."
227- echo ${log_line} >> /var/log/sudo_pair.log
228+ logger -p auth.warn $log_line
229+ {% if pagerduty_key %}
230+ /usr/local/bin/pagerdutyevent "$log_line"
231+ {% endif %}
232 {% endif %}
233 fi
234
235diff --git a/src/templates/sudo_pair.pagerduty.tmpl b/src/templates/sudo_pair.pagerduty.tmpl
236new file mode 100644
237index 0000000..2185cf9
238--- /dev/null
239+++ b/src/templates/sudo_pair.pagerduty.tmpl
240@@ -0,0 +1,6 @@
241+[DEFAULT]
242+pagerduty_key: {{ pagerduty_key }}
243+pagerduty_source: {{ pagerduty_source }}
244+{% if https_proxy -%}
245+https_proxy: {{ https_proxy }}
246+{% endif %}
247\ No newline at end of file
248diff --git a/src/tests/unit/conftest.py b/src/tests/unit/conftest.py
249index c1df1d0..bc65d15 100644
250--- a/src/tests/unit/conftest.py
251+++ b/src/tests/unit/conftest.py
252@@ -16,7 +16,7 @@ def mock_hookenv_config(monkeypatch):
253
254 def mock_config():
255 cfg = {}
256- yml = yaml.load(open("./config.yaml"))
257+ yml = yaml.safe_load(open("./config.yaml"))
258
259 # Load all defaults
260 for key, value in yml["options"].items():
261@@ -54,4 +54,5 @@ def sph(mock_hookenv_config, mock_charm_dir, tmpdir):
262 sph.sudoers_bypass_path = tmpdir.join(sph.sudoers_bypass_path)
263 sph.socket_dir_perms = 0o775
264 sph.sudo_conf_perms = 0o644
265+ sph.pagerduty_path = tmpdir.join(sph.pagerduty_path)
266 return sph
267diff --git a/src/tests/unit/test_actions.py b/src/tests/unit/test_actions.py
268index ddf6003..9c194f3 100644
269--- a/src/tests/unit/test_actions.py
270+++ b/src/tests/unit/test_actions.py
271@@ -12,10 +12,12 @@ import actions # NOQA
272 @mock.patch("libsudopair.SudoPairHelper")
273 @mock.patch("actions.action_set")
274 @mock.patch("actions.status_set")
275-def test_remove_action(status_set, action_set, sudo_pair_helper):
276+@mock.patch("actions.subprocess.run")
277+def test_remove_action(subprocess_run, status_set, action_set, sudo_pair_helper):
278 """Verify remove action."""
279 actions.remove()
280 msg = "Successfully removed sudo-pair config and binaries"
281 action_set.assert_called_with({"message": msg})
282 status_set.assert_called_with("active", msg)
283 sudo_pair_helper().deconfigure.assert_called()
284+ subprocess_run.assert_called_with(["/usr/local/bin/pagerdutyevent", msg])
285diff --git a/src/tests/unit/test_libsudopair.py b/src/tests/unit/test_libsudopair.py
286index 860d8db..8d730ad 100644
287--- a/src/tests/unit/test_libsudopair.py
288+++ b/src/tests/unit/test_libsudopair.py
289@@ -144,7 +144,7 @@ class TestSudoPairHelper:
290 def test_render_sudo_approve(self, sph, tmpdir):
291 """Check that sudo_approve file is rendered correctly."""
292 # Auto Approve true
293- expected_content = "echo ${log_line} >> /var/log/sudo_pair.log"
294+ expected_content = "logger -p auth.warn $log_line"
295 socket_dir = tmpdir.join("/var/run/sudo_pair")
296 expected_content_socket_dir = 'declare -r SUDO_SOCKET_PATH="{}"'.format(
297 socket_dir

Subscribers

People subscribed via source and target branches