From becdd4b1ebe030e1d3699f44155d6b0043d0efd1 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Tue, 4 Mar 2014 17:30:58 -0800 Subject: [PATCH] Add a job to check IRC channel access Add an IRC channel config file with some stub access information for a few channels. A new tool can read this file and verify that openstackinfra has founder access on all the channels listed in the file. Run a job on changes to that file to verify that access is correct. Later, other tools (like statusbot or gerritbot) may read that file directly to determine which channels to join. Or we can at least add checks that all channels referenced by another tool (like eavesdrop) are also listed in this file. Related-Bug: 1190296 Change-Id: I38440e745402af5bbc3f0d0cc04a150c0a4bb47c --- .../openstack_project/files/irc/channels.yaml | 45 ++++++ .../files/irc/checkaccess.py | 144 ++++++++++++++++++ .../jenkins_job_builder/config/infra.yaml | 15 ++ .../openstack_project/files/zuul/layout.yaml | 6 + tox.ini | 5 + 5 files changed, 215 insertions(+) create mode 100644 modules/openstack_project/files/irc/channels.yaml create mode 100644 modules/openstack_project/files/irc/checkaccess.py diff --git a/modules/openstack_project/files/irc/channels.yaml b/modules/openstack_project/files/irc/channels.yaml new file mode 100644 index 0000000000..0492e420da --- /dev/null +++ b/modules/openstack_project/files/irc/channels.yaml @@ -0,0 +1,45 @@ +# Copyright 2014 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Global definitions +# First set up the access levels (map names in this file to chanserv flags): +access: + masters: +AFRfiorstv + operators: +Aiortv + topics: +t + +# Define access that should apply to all channels: +global: + masters: + - openstackinfra + operators: + - jeblair + - mtaylor + - clarkb + - fungi + - SergeyLukjanov + - ttx + - reed + topics: + - openstackstatus + +# Individual channel configuration: +channels: + - name: openstack-infra + - name: openstack-meeting + topics: + - open_stack + - name: openstack-nova + operators: + - russellb diff --git a/modules/openstack_project/files/irc/checkaccess.py b/modules/openstack_project/files/irc/checkaccess.py new file mode 100644 index 0000000000..8e5575c0ea --- /dev/null +++ b/modules/openstack_project/files/irc/checkaccess.py @@ -0,0 +1,144 @@ +#! /usr/bin/env python + +# Copyright 2011, 2013-2014 OpenStack Foundation +# Copyright 2012 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import irc.client +import logging +import random +import string +import sys +import yaml + +logging.basicConfig(level=logging.INFO) + + +class CheckAccess(irc.client.SimpleIRCClient): + log = logging.getLogger("checkaccess") + + def __init__(self, channels, nick, flags): + irc.client.SimpleIRCClient.__init__(self) + self.identify_msg_cap = False + self.channels = channels + self.nick = nick + self.flags = flags + self.current_channel = None + self.current_list = [] + self.failed = True + + def on_disconnect(self, connection, event): + if self.failed: + sys.exit(1) + else: + sys.exit(0) + + def on_welcome(self, c, e): + self.identify_msg_cap = False + self.log.debug("Requesting identify-msg capability") + c.cap('REQ', 'identify-msg') + c.cap('END') + + def on_cap(self, c, e): + self.log.debug("Received cap response %s" % repr(e.arguments)) + if e.arguments[0] == 'ACK' and 'identify-msg' in e.arguments[1]: + self.log.debug("identify-msg cap acked") + self.identify_msg_cap = True + self.advance() + + def on_privnotice(self, c, e): + if not self.identify_msg_cap: + self.log.debug("Ignoring message because identify-msg " + "cap not enabled") + return + nick = e.source.split('!')[0] + auth = e.arguments[0][0] + msg = e.arguments[0][1:] + if auth != '+' or nick != 'ChanServ': + self.log.debug("Ignoring message from unauthenticated " + "user %s" % nick) + return + self.failed = False + self.advance(msg) + + def advance(self, msg=None): + if not self.current_channel: + if not self.channels: + self.connection.quit() + return + self.current_channel = self.channels.pop() + self.current_list = [] + self.connection.privmsg('chanserv', 'access list %s' % + self.current_channel) + return + if msg.startswith('End of'): + found = False + for nick, flags, msg in self.current_list: + if nick == self.nick and flags == self.flags: + self.log.info('%s access ok on %s' % + (self.nick, self.current_channel)) + found = True + break + if not found: + self.failed = True + print ("%s does not have permissions on %s:" % + (self.nick, self.current_channel)) + for nick, flags, msg in self.current_list: + print msg + print + self.current_channel = None + self.advance() + return + parts = msg.split() + self.current_list.append((parts[1], parts[2], msg)) + + +def main(): + parser = argparse.ArgumentParser(description='IRC channel access check') + parser.add_argument('-l', dest='config', + default='/etc/irc/channels.yaml', + help='path to the config file') + parser.add_argument('-s', dest='server', + default='chat.freenode.net', + help='IRC server') + parser.add_argument('-p', dest='port', + default=6667, + help='IRC port') + parser.add_argument('nick', + help='the nick for which access should be validated') + args = parser.parse_args() + + config = yaml.load(open(args.config)) + channels = [] + for channel in config['channels']: + channels.append('#' + channel['name']) + + access_level = None + for level, names in config['global'].items(): + if args.nick in names: + access_level = level + if access_level is None: + raise Exception("Unable to determine global access level for %s" % + args.nick) + flags = config['access'][access_level] + + a = CheckAccess(channels, args.nick, flags) + mynick = ''.join(random.choice(string.ascii_uppercase) + for x in range(16)) + a.connect(args.server, int(args.port), mynick) + a.start() + +if __name__ == "__main__": + main() diff --git a/modules/openstack_project/files/jenkins_job_builder/config/infra.yaml b/modules/openstack_project/files/jenkins_job_builder/config/infra.yaml index f31cf7baaf..e47a44a2fe 100644 --- a/modules/openstack_project/files/jenkins_job_builder/config/infra.yaml +++ b/modules/openstack_project/files/jenkins_job_builder/config/infra.yaml @@ -10,6 +10,21 @@ - console-log +- job: + name: gate-config-irc-access + node: bare-precise + + builders: + - gerrit-git-prep + - tox: + envlist: 'irc' + github-org: 'openstack-infra' + project: 'config' + + publishers: + - console-log + + - job: name: gate-config-layout node: bare-precise diff --git a/modules/openstack_project/files/zuul/layout.yaml b/modules/openstack_project/files/zuul/layout.yaml index c8247a76e2..9f88e36cc0 100644 --- a/modules/openstack_project/files/zuul/layout.yaml +++ b/modules/openstack_project/files/zuul/layout.yaml @@ -410,6 +410,10 @@ jobs: voting: false failure-message: Jenkins XML output has changed. success-message: Jenkins XML output is unchanged. + - name: gate-config-irc-access + voting: false + files: + - 'modules/openstack_project/files/irc/channels.yaml' # Continous publishing from master of the following documentation targets: - name: openstack-admin-guide-cloud branch: ^master$ @@ -2590,6 +2594,7 @@ projects: - gate-config-pep8 - gate-config-puppet-lint - gate-config-puppet-syntax + - gate-config-irc-access - gate-ci-docs - check-projects-yaml-alphabetized gate: @@ -2597,6 +2602,7 @@ projects: - gate-config-pep8 - gate-config-puppet-lint - gate-config-puppet-syntax + - gate-config-irc-access - check-projects-yaml-alphabetized post: - ci-docs diff --git a/tox.ini b/tox.ini index e137686c5a..23b8faef26 100644 --- a/tox.ini +++ b/tox.ini @@ -14,6 +14,11 @@ commands = flake8 [testenv:venv] commands = {posargs} +[testenv:irc] +deps = PyYAML + irc +commands = python modules/openstack_project/files/irc/checkaccess.py -l modules/openstack_project/files/irc/channels.yaml openstackinfra + [flake8] show-source = True exclude = .tox