django-orchestra/orchestra/apps/websites/backends/apache.py

257 lines
10 KiB
Python
Raw Normal View History

2014-07-25 15:17:50 +00:00
import textwrap
2014-05-08 16:59:35 +00:00
import os
2014-09-26 15:05:20 +00:00
import re
2014-05-08 16:59:35 +00:00
from django.template import Template, Context
from django.utils.translation import ugettext_lazy as _
2014-07-09 16:17:43 +00:00
from orchestra.apps.orchestration import ServiceController
from orchestra.apps.resources import ServiceMonitor
2014-05-08 16:59:35 +00:00
from .. import settings
2014-07-09 16:17:43 +00:00
class Apache2Backend(ServiceController):
2014-05-08 16:59:35 +00:00
model = 'websites.Website'
2014-09-24 20:09:41 +00:00
related_models = (
('websites.Content', 'website'),
)
2014-05-08 16:59:35 +00:00
verbose_name = _("Apache 2")
def save(self, site):
context = self.get_context(site)
extra_conf = self.get_content_directives(site)
if site.protocol is 'https':
extra_conf += self.get_ssl(site)
extra_conf += self.get_security(site)
2014-10-30 16:34:02 +00:00
extra_conf += self.get_redirect(site)
2014-05-08 16:59:35 +00:00
context['extra_conf'] = extra_conf
apache_conf = Template(textwrap.dedent("""\
# {{ banner }}
2014-10-17 15:24:41 +00:00
<VirtualHost {{ ip }}:{{ site.port }}>
2014-10-10 14:39:46 +00:00
ServerName {{ site.domains.all|first }}\
{% if site.domains.all|slice:"1:" %}
2014-10-10 14:39:46 +00:00
ServerAlias {{ site.domains.all|slice:"1:"|join:' ' }}{% endif %}
CustomLog {{ logs }} common
2014-10-10 14:39:46 +00:00
SuexecUserGroup {{ user }} {{ group }}\
{% for line in extra_conf.splitlines %}
{{ line | safe }}{% endfor %}
2014-11-05 20:22:01 +00:00
IncludeOptional /etc/apache2/extra-vhos[t]/{{ site_unique_name }}.con[f]
</VirtualHost>"""
))
2014-05-08 16:59:35 +00:00
apache_conf = apache_conf.render(Context(context))
apache_conf += self.get_protections(site)
context['apache_conf'] = apache_conf
2014-10-17 14:59:06 +00:00
self.append(textwrap.dedent("""\
{
echo -e '%(apache_conf)s' | diff -N -I'^\s*#' %(sites_available)s -
} || {
echo -e '%(apache_conf)s' > %(sites_available)s
UPDATED=1
}""" % context
))
2014-05-08 16:59:35 +00:00
self.enable_or_disable(site)
def delete(self, site):
context = self.get_context(site)
self.append("a2dissite %(site_unique_name)s.conf && UPDATED=1" % context)
self.append("rm -fr %(sites_available)s" % context)
def commit(self):
""" reload Apache2 if necessary """
self.append('[[ $UPDATED == 1 ]] && service apache2 reload')
def get_content_directives(self, site):
directives = ''
for content in site.content_set.all().order_by('-path'):
2014-10-24 10:16:46 +00:00
method, args = content.webapp.get_directive()
2014-05-08 16:59:35 +00:00
method = getattr(self, 'get_%s_directives' % method)
directives += method(content, *args)
return directives
2014-10-24 10:16:46 +00:00
def get_static_directives(self, content, *args):
2014-05-08 16:59:35 +00:00
context = self.get_content_context(content)
context['path'] = args[0] % context if args else content.webapp.get_path()
return "Alias %(location)s %(path)s\n" % context
def get_fpm_directives(self, content, *args):
context = self.get_content_context(content)
context['fcgi_path'] = args[0] % context
directive = "ProxyPassMatch ^%(location)s(.*\.php(/.*)?)$ %(fcgi_path)s$1\n"
return directive % context
2014-10-24 10:16:46 +00:00
def get_fcgi_directives(self, content, fcgid_path):
2014-05-08 16:59:35 +00:00
context = self.get_content_context(content)
context['fcgid_path'] = fcgid_path % context
2014-10-24 10:16:46 +00:00
fcgid = self.get_static_directives(content)
2014-10-10 14:39:46 +00:00
fcgid += textwrap.dedent("""\
ProxyPass %(location)s !
<Directory %(app_path)s>
Options +ExecCGI
AddHandler fcgid-script .php
2014-10-30 16:34:02 +00:00
FcgidWrapper %(fcgid_path)s\
2014-10-10 14:39:46 +00:00
""" % context)
2014-05-08 16:59:35 +00:00
for option in content.webapp.options.filter(name__startswith='Fcgid'):
fcgid += " %s %s\n" % (option.name, option.value)
fcgid += "</Directory>\n"
return fcgid
def get_ssl(self, site):
cert = settings.WEBSITES_DEFAULT_HTTPS_CERT
custom_cert = site.options.filter(name='ssl')
if custom_cert:
cert = tuple(custom_cert[0].value.split())
2014-10-30 16:34:02 +00:00
# TODO separate directtives?
2014-10-10 14:39:46 +00:00
directives = textwrap.dedent("""\
SSLEngine on
SSLCertificateFile %s
2014-10-30 16:34:02 +00:00
SSLCertificateKeyFile %s\
""" % cert
2014-10-10 14:39:46 +00:00
)
2014-05-08 16:59:35 +00:00
return directives
def get_security(self, site):
directives = ''
for rules in site.options.filter(name='sec_rule_remove'):
2014-10-17 14:59:06 +00:00
for rule in rules.value.split():
2014-10-30 16:34:02 +00:00
directives += "SecRuleRemoveById %i\n" % int(rule)
2014-05-08 16:59:35 +00:00
for modsecurity in site.options.filter(name='sec_rule_off'):
directives += textwrap.dedent("""\
<LocationMatch %s>
SecRuleEngine Off
2014-10-30 16:34:02 +00:00
</LocationMatch>\
""" % modsecurity.value)
2014-10-30 16:34:02 +00:00
if directives:
directives = '<IfModule mod_security2.c>\n%s\n</IfModule>' % directives
return directives
def get_redirect(self, site):
directives = ''
for redirect in site.options.filter(name='redirect'):
if re.match(r'^.*[\^\*\$\?\)]+.*$', redirect.value):
directives += "RedirectMatch %s" % redirect.value
else:
directives += "Redirect %s" % redirect.value
2014-05-08 16:59:35 +00:00
return directives
def get_protections(self, site):
protections = ""
__, regex = settings.WEBSITES_OPTIONS['directory_protection']
2014-09-26 15:05:20 +00:00
context = self.get_context(site)
2014-05-08 16:59:35 +00:00
for protection in site.options.filter(name='directory_protection'):
path, name, passwd = re.match(regex, protection.value).groups()
path = os.path.join(context['root'], path)
passwd = os.path.join(self.USER_HOME % context, passwd)
2014-10-10 14:39:46 +00:00
protections += textwrap.dedent("""
<Directory %s>
AllowOverride All
#AuthPAM_Enabled off
AuthType Basic
AuthName %s
AuthUserFile %s
<Limit GET POST>
require valid-user
</Limit>
</Directory>""" % (path, name, passwd)
2014-05-08 16:59:35 +00:00
)
return protections
def enable_or_disable(self, site):
context = self.get_context(site)
2014-10-17 14:59:06 +00:00
self.append("ls -l %(sites_enabled)s > /dev/null; DISABLED=$?" % context)
2014-05-08 16:59:35 +00:00
if site.is_active:
self.append("if [[ $DISABLED ]]; then a2ensite %(site_unique_name)s.conf;\n"
"else UPDATED=0; fi" % context)
else:
self.append("if [[ ! $DISABLED ]]; then a2dissite %(site_unique_name)s.conf;\n"
"else UPDATED=0; fi" % context)
def get_context(self, site):
base_apache_conf = settings.WEBSITES_BASE_APACHE_CONF
sites_available = os.path.join(base_apache_conf, 'sites-available')
sites_enabled = os.path.join(base_apache_conf, 'sites-enabled')
context = {
'site': site,
'site_name': site.name,
2014-10-17 15:23:02 +00:00
'ip': settings.WEBSITES_DEFAULT_IP,
2014-05-08 16:59:35 +00:00
'site_unique_name': site.unique_name,
2014-10-23 15:38:46 +00:00
'user': site.get_username(),
'group': site.get_groupname(),
2014-05-08 16:59:35 +00:00
'sites_enabled': sites_enabled,
'sites_available': "%s.conf" % os.path.join(sites_available, site.unique_name),
2014-10-27 14:31:04 +00:00
'logs': site.get_www_log_path(),
2014-05-08 16:59:35 +00:00
'banner': self.get_banner(),
}
return context
def get_content_context(self, content):
context = self.get_context(content.website)
context.update({
'type': content.webapp.type,
'location': content.path,
'app_name': content.webapp.name,
'app_path': content.webapp.get_path(),
'fpm_port': content.webapp.get_fpm_port(),
})
return context
2014-07-09 16:17:43 +00:00
class Apache2Traffic(ServiceMonitor):
model = 'websites.Website'
resource = ServiceMonitor.TRAFFIC
verbose_name = _("Apache 2 Traffic")
2014-07-25 15:17:50 +00:00
def prepare(self):
2014-10-27 13:29:02 +00:00
current_date = self.current_date.strftime("%Y-%m-%d %H:%M:%S %Z")
2014-10-10 14:39:46 +00:00
self.append(textwrap.dedent("""\
2014-07-25 15:17:50 +00:00
function monitor () {
OBJECT_ID=$1
INI_DATE=$2
LOG_FILE="$3"
{
2014-10-27 13:29:02 +00:00
awk -v ini="${INI_DATE}" -v end="$(date '+%%Y%%m%%d%%H%%M%%S' -d '%s')" '
2014-07-25 15:17:50 +00:00
BEGIN {
sum = 0
months["Jan"] = "01";
months["Feb"] = "02";
months["Mar"] = "03";
months["Apr"] = "04";
months["May"] = "05";
months["Jun"] = "06";
months["Jul"] = "07";
months["Aug"] = "08";
months["Sep"] = "09";
months["Oct"] = "10";
months["Nov"] = "11";
months["Dec"] = "12";
} {
# date = [11/Jul/2014:13:50:41
date = substr($4, 2)
year = substr(date, 8, 4)
month = months[substr(date, 4, 3)];
day = substr(date, 1, 2)
hour = substr(date, 13, 2)
minute = substr(date, 16, 2)
second = substr(date, 19, 2)
line_date = year month day hour minute second
if ( line_date > ini && line_date < end)
sum += $NF
} END {
print sum
}' "${LOG_FILE}" || echo 0
} | xargs echo ${OBJECT_ID}
}""" % current_date))
2014-07-09 16:17:43 +00:00
def monitor(self, site):
context = self.get_context(site)
2014-10-27 13:29:02 +00:00
self.append('monitor {object_id} $(date "+%Y%m%d%H%M%S" -d "{last_date}") "{log_file}"'.format(**context))
2014-07-09 16:17:43 +00:00
def get_context(self, site):
return {
2014-10-27 14:31:04 +00:00
'log_file': '%s{,.1}' % site.get_www_log_path(),
2014-10-27 17:34:14 +00:00
'last_date': self.get_last_date(site.pk).strftime("%Y-%m-%d %H:%M:%S %Z"),
2014-07-11 14:48:46 +00:00
'object_id': site.pk,
2014-07-09 16:17:43 +00:00
}