system-config/docker/accessbot/accessbot.py
Monty Taylor 9fd2135a46 Split eavesdrop into its own playbook
Extract eavedrop into its own service playbook and
puppet manifest. While doing that, stop using jenkinsuser
on eavesdrop in favor of zuul-user.

Add the ability to override the keys for the zuul user.

Remove openstack_project::server, it doesn't do anything.

Containerize and anisblize accessbot. The structure of
how we're doing it in puppet makes it hard to actually
run the puppet in the gate. Run the script in its own
playbook so that we can avoid running it in the gate.

Change-Id: I53cb63ffa4ae50575d4fa37b24323ad13ec1bac3
2020-04-23 14:34:28 -05:00

249 lines
8.7 KiB
Python
Executable File

#! /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 configparser
import argparse
import irc.client
import logging
import ssl
import sys
import time
import yaml
logging.basicConfig(
format='%(asctime)s [%(levelname)s] %(name)s - %(message)s',
level=logging.DEBUG)
class SetAccess(irc.client.SimpleIRCClient):
log = logging.getLogger("setaccess")
def __init__(self, config, noop, nick, password, server, port):
irc.client.SimpleIRCClient.__init__(self)
self.identify_msg_cap = False
self.config = config
self.nick = nick
self.password = password
self.server = server
self.port = int(port)
self.noop = noop
self.channels = [x['name'] for x in self.config['channels']]
self.current_channel = None
self.current_list = []
self.changes = []
self.identified = False
if self.port == 6697:
factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
self.connect(self.server, self.port, self.nick,
connect_factory=factory)
else:
self.connect(self.server, self.port, self.nick)
def on_disconnect(self, connection, event):
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.log.debug("Identifying to nickserv")
c.privmsg("nickserv", "identify %s " % self.password)
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 == '+' and nick == 'NickServ' and not self.identified:
if msg.startswith('You are now identified'):
self.identified = True
# Prejoin and set ourselves as op in these channels,
# to facilitate +f forwarding.
for channel in self.config.get('op_channels', []):
c.join("#%s" % channel)
c.privmsg("chanserv", "op #%s" % channel)
self.advance()
return
if auth != '+' or nick != 'ChanServ':
self.log.debug("Ignoring message from unauthenticated "
"user %s" % nick)
return
self.failed = False
self.advance(msg)
def _get_access_list(self, channel_name):
ret = {}
alumni = []
mode = ''
channel = None
for c in self.config['channels']:
if c['name'] == channel_name:
channel = c
if channel is None:
raise Exception("Unknown channel %s" % (channel_name,))
mask = ''
for access, nicks in (self.config['global'].items() +
channel.items()):
if access == 'mask':
mask = self.config['access'].get(nicks)
continue
if access == 'alumni':
alumni += nicks
continue
if access == 'mode':
mode = nicks
continue
flags = self.config['access'].get(access)
if flags is None:
continue
for nick in nicks:
ret[nick] = flags
return mask, ret, alumni, mode
def _get_access_change(self, current, target, mask):
remove = ''
add = ''
change = ''
for x in current:
if x in '+-':
continue
if target:
if x not in target:
remove += x
else:
if x not in mask:
remove += x
for x in target:
if x in '+-':
continue
if x not in current:
add += x
if remove:
change += '-' + remove
if add:
change += '+' + add
return change
def _get_access_changes(self):
mask, target, alumni, mode = self._get_access_list(self.current_channel)
self.log.debug("Mask for %s: %s" % (self.current_channel, mask))
self.log.debug("Target for %s: %s" % (self.current_channel, target))
all_nicks = set()
global_alumni = self.config.get('alumni', {})
global_mode = self.config.get('mode', '')
current = {}
changes = []
for nick, flags, msg in self.current_list:
if nick in global_alumni or nick in alumni :
self.log.debug("%s is an alumni; removing access", nick)
changes.append('access #%s del %s' % (self.current_channel, nick))
continue
all_nicks.add(nick)
current[nick] = flags
for nick in target.keys():
all_nicks.add(nick)
for nick in all_nicks:
change = self._get_access_change(current.get(nick, ''),
target.get(nick, ''), mask)
if change:
changes.append('access #%s add %s %s' % (self.current_channel,
nick, change))
# Set the mode. Note we always just hard-set the mode for
# simplicity (per the man page mlock always clears and sets
# anyway). Channel mode overrides global mode.
#
# Note for +f you need to be op in the target channel; see
# op_channel option.
if not mode and global_mode:
mode = global_mode
self.log.debug("Setting mode to : %s" % mode)
if mode:
changes.append('set #%s mlock %s' % (self.current_channel, mode))
return changes
def advance(self, msg=None):
if self.changes:
if self.noop:
for change in self.changes:
self.log.info('NOOP: ' + change)
self.changes = []
else:
change = self.changes.pop()
self.log.info(change)
self.connection.privmsg('chanserv', change)
time.sleep(1)
return
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)
time.sleep(1)
return
if msg.startswith('End of'):
self.changes = self._get_access_changes()
self.current_channel = None
self.advance()
return
parts = msg.split()
if parts[2].startswith('+'):
self.current_list.append((parts[1], parts[2], msg))
def main():
parser = argparse.ArgumentParser(description='IRC channel access check')
parser.add_argument('-c', dest='config', nargs=1,
help='specify the config file')
parser.add_argument('-l', dest='channels',
default='/etc/irc/channels.yaml',
help='path to the channel config')
parser.add_argument('--noop', dest='noop',
action='store_true',
help="Don't make any changes")
args = parser.parse_args()
config = configparser.ConfigParser()
config.read(args.config)
channels = yaml.load(open(args.channels))
a = SetAccess(channels, args.noop,
config.get('ircbot', 'nick'),
config.get('ircbot', 'pass'),
config.get('ircbot', 'server'),
config.get('ircbot', 'port'))
a.start()
if __name__ == "__main__":
main()