import os import shutil import tempfile import urllib.parse as urlparse from django.conf import settings from awx.main.utils.reload import supervisor_service_command def construct_rsyslog_conf_template(settings=settings): tmpl = '' parts = [] enabled = getattr(settings, 'LOG_AGGREGATOR_ENABLED') host = getattr(settings, 'LOG_AGGREGATOR_HOST', '') port = getattr(settings, 'LOG_AGGREGATOR_PORT', '') protocol = getattr(settings, 'LOG_AGGREGATOR_PROTOCOL', '') timeout = getattr(settings, 'LOG_AGGREGATOR_TCP_TIMEOUT', 5) max_disk_space = getattr(settings, 'LOG_AGGREGATOR_MAX_DISK_USAGE_GB', 1) spool_directory = getattr(settings, 'LOG_AGGREGATOR_MAX_DISK_USAGE_PATH', '/var/lib/awx').rstrip('/') if not os.access(spool_directory, os.W_OK): spool_directory = '/var/lib/awx' max_bytes = settings.MAX_EVENT_RES_DATA if settings.LOG_AGGREGATOR_RSYSLOGD_DEBUG: parts.append('$DebugLevel 2') parts.extend([ '$WorkDirectory /var/lib/awx/rsyslog', f'$MaxMessageSize {max_bytes}', '$IncludeConfig /var/lib/awx/rsyslog/conf.d/*.conf', f'main_queue(queue.spoolDirectory="{spool_directory}" queue.maxdiskspace="{max_disk_space}g" queue.type="Disk" queue.filename="awx-external-logger-backlog")', # noqa 'module(load="imuxsock" SysSock.Use="off")', 'input(type="imuxsock" Socket="' + settings.LOGGING['handlers']['external_logger']['address'] + '" unlink="on")', 'template(name="awx" type="string" string="%rawmsg-after-pri%")', ]) def escape_quotes(x): return x.replace('"', '\\"') if not enabled: parts.append('action(type="omfile" file="/dev/null")') # rsyslog needs *at least* one valid action to start tmpl = '\n'.join(parts) return tmpl if protocol.startswith('http'): scheme = 'https' # urlparse requires '//' to be provided if scheme is not specified original_parsed = urlparse.urlsplit(host) if (not original_parsed.scheme and not host.startswith('//')) or original_parsed.hostname is None: host = '%s://%s' % (scheme, host) if scheme else '//%s' % host parsed = urlparse.urlsplit(host) host = escape_quotes(parsed.hostname) try: if parsed.port: port = parsed.port except ValueError: port = settings.LOG_AGGREGATOR_PORT # https://github.com/rsyslog/rsyslog-doc/blob/master/source/configuration/modules/omhttp.rst ssl = 'on' if parsed.scheme == 'https' else 'off' skip_verify = 'off' if settings.LOG_AGGREGATOR_VERIFY_CERT else 'on' allow_unsigned = 'off' if settings.LOG_AGGREGATOR_VERIFY_CERT else 'on' if not port: port = 443 if parsed.scheme == 'https' else 80 params = [ 'type="omhttp"', f'server="{host}"', f'serverport="{port}"', f'usehttps="{ssl}"', f'allowunsignedcerts="{allow_unsigned}"', f'skipverifyhost="{skip_verify}"', 'action.resumeRetryCount="-1"', 'template="awx"', 'errorfile="/var/log/tower/rsyslog.err"', f'action.resumeInterval="{timeout}"' ] if parsed.path: path = urlparse.quote(parsed.path[1:], safe='/=') if parsed.query: path = f'{path}?{urlparse.quote(parsed.query)}' params.append(f'restpath="{path}"') username = escape_quotes(getattr(settings, 'LOG_AGGREGATOR_USERNAME', '')) password = escape_quotes(getattr(settings, 'LOG_AGGREGATOR_PASSWORD', '')) if getattr(settings, 'LOG_AGGREGATOR_TYPE', None) == 'splunk': # splunk has a weird authorization header if password: # from omhttp docs: # https://www.rsyslog.com/doc/v8-stable/configuration/modules/omhttp.html # > Currently only a single additional header/key pair is # > configurable, further development is needed to support # > arbitrary header key/value lists. params.append('httpheaderkey="Authorization"') params.append(f'httpheadervalue="Splunk {password}"') elif username: params.append(f'uid="{username}"') if password: # you can only have a basic auth password if there's a username params.append(f'pwd="{password}"') params = ' '.join(params) parts.extend(['module(load="omhttp")', f'action({params})']) elif protocol and host and port: parts.append( f'action(type="omfwd" target="{host}" port="{port}" protocol="{protocol}" action.resumeRetryCount="-1" action.resumeInterval="{timeout}" template="awx")' # noqa ) else: parts.append('action(type="omfile" file="/dev/null")') # rsyslog needs *at least* one valid action to start tmpl = '\n'.join(parts) return tmpl def reconfigure_rsyslog(): tmpl = construct_rsyslog_conf_template() # Write config to a temp file then move it to preserve atomicity with tempfile.TemporaryDirectory(prefix='rsyslog-conf-') as temp_dir: path = temp_dir + '/rsyslog.conf.temp' with open(path, 'w') as f: os.chmod(path, 0o640) f.write(tmpl + '\n') shutil.move(path, '/var/lib/awx/rsyslog/rsyslog.conf') supervisor_service_command(command='restart', service='awx-rsyslogd')