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

338 lines
13 KiB
Python
Raw Normal View History

2014-05-08 16:59:35 +00:00
import os
2014-09-26 15:05:20 +00:00
import re
import textwrap
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
2015-03-10 21:51:10 +00:00
from ..utils import normurlpath
2014-05-08 16:59:35 +00:00
2014-07-09 16:17:43 +00:00
class Apache2Backend(ServiceController):
2015-03-10 21:51:10 +00:00
HTTP_PORT = 80
HTTPS_PORT = 443
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")
2015-03-10 21:51:10 +00:00
def render_virtual_host(self, site, context, ssl=False):
context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT
2014-05-08 16:59:35 +00:00
extra_conf = self.get_content_directives(site)
2015-03-10 21:51:10 +00:00
directives = site.get_directives()
if ssl:
extra_conf += self.get_ssl(directives)
extra_conf += self.get_security(directives)
extra_conf += self.get_redirects(directives)
extra_conf += self.get_proxies(directives)
2014-05-08 16:59:35 +00:00
context['extra_conf'] = extra_conf
2015-03-10 21:51:10 +00:00
return Template(textwrap.dedent("""\
<VirtualHost {{ ip }}:{{ port }}>
2014-10-10 14:39:46 +00:00
ServerName {{ site.domains.all|first }}\
{% if site.domains.all|slice:"1:" %}
2015-03-02 10:37:25 +00:00
ServerAlias {{ site.domains.all|slice:"1:"|join:' ' }}{% endif %}\
{% if access_log %}
CustomLog {{ access_log }} common{% endif %}\
{% if error_log %}
ErrorLog {{ error_log }}{% endif %}
2014-10-10 14:39:46 +00:00
SuexecUserGroup {{ user }} {{ group }}\
{% for line in extra_conf.splitlines %}
{{ line | safe }}{% endfor %}
2015-03-02 10:37:25 +00:00
#IncludeOptional /etc/apache2/extra-vhos[t]/{{ site_unique_name }}.con[f]
2015-03-10 21:51:10 +00:00
</VirtualHost>
""")
).render(Context(context))
def render_redirect_https(self, context):
context['port'] = self.HTTP_PORT
return Template(textwrap.dedent("""
<VirtualHost {{ ip }}:{{ port }}>
ServerName {{ site.domains.all|first }}\
{% if site.domains.all|slice:"1:" %}
ServerAlias {{ site.domains.all|slice:"1:"|join:' ' }}{% endif %}\
{% if access_log %}
CustomLog {{ access_log }} common{% endif %}\
{% if error_log %}
ErrorLog {{ error_log }}{% endif %}
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</VirtualHost>
""")
).render(Context(context))
def save(self, site):
context = self.get_context(site)
apache_conf = '# %(banner)s\n' % context
if site.protocol in (site.HTTP, site.HTTP_AND_HTTPS):
apache_conf += self.render_virtual_host(site, context, ssl=False)
if site.protocol in (site.HTTP_AND_HTTPS, site.HTTPS_ONLY, site.HTTPS):
apache_conf += self.render_virtual_host(site, context, ssl=True)
if site.protocol == site.HTTPS_ONLY:
apache_conf += self.render_redirect_https(context)
2014-05-08 16:59:35 +00:00
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
2014-11-27 19:17:26 +00:00
}""") % 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('if [[ $UPDATED == 1 ]]; then service apache2 reload; fi')
2014-05-08 16:59:35 +00:00
def get_content_directives(self, site):
directives = ''
for content in site.content_set.all().order_by('-path'):
directive = content.webapp.get_directive()
method, args = directive[0], directive[1:]
2014-05-08 16:59:35 +00:00
method = getattr(self, 'get_%s_directives' % method)
directives += method(content, *args)
return directives
def get_static_directives(self, content, app_path):
2014-05-08 16:59:35 +00:00
context = self.get_content_context(content)
context['app_path'] = app_path % context
2015-03-10 21:51:10 +00:00
return "Alias %(location)s/ %(app_path)s/\n" % context
2014-05-08 16:59:35 +00:00
def get_fpm_directives(self, content, socket_type, socket, app_path):
if socket_type == 'unix':
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/'
if content.path != '/':
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/$1'
elif socket_type == 'tcp':
target = 'fcgi://%(socket)s%(app_path)s/$1'
else:
raise TypeError("%s socket not supported." % socket_type)
2014-05-08 16:59:35 +00:00
context = self.get_content_context(content)
context.update({
'app_path': app_path,
'socket': socket,
})
return textwrap.dedent("""\
2015-03-10 21:51:10 +00:00
ProxyPassMatch ^%(location)s/(.*\.php(/.*)?)$ {target}
Alias %(location)s/ %(app_path)s/
""".format(target=target) % context
)
2014-05-08 16:59:35 +00:00
def get_fcgid_directives(self, content, app_path, wrapper_path):
2014-05-08 16:59:35 +00:00
context = self.get_content_context(content)
context.update({
'app_path': app_path,
'wrapper_path': wrapper_path,
})
return textwrap.dedent("""\
2015-03-10 21:51:10 +00:00
Alias %(location)s/ %(app_path)s/
ProxyPass %(location)s/ !
<Directory %(app_path)s/>
2014-10-10 14:39:46 +00:00
Options +ExecCGI
AddHandler fcgid-script .php
FcgidWrapper %(wrapper_path)s
</Directory>
2014-11-27 19:17:26 +00:00
""") % context
2014-05-08 16:59:35 +00:00
2015-03-10 21:51:10 +00:00
def get_ssl(self, directives):
2015-03-10 22:27:32 +00:00
config = ''
2015-03-10 21:51:10 +00:00
ca = directives.get('ssl_ca')
if ca:
2015-03-10 22:27:32 +00:00
config += "SSLCACertificateFile %s\n" % ca[0]
2015-03-10 21:51:10 +00:00
cert = directives.get('ssl_cert')
if cert:
2015-03-10 22:27:32 +00:00
config += "SSLCertificateFile %\n" % cert[0]
2015-03-10 21:51:10 +00:00
key = directives.get('ssl_key')
if key:
2015-03-10 22:27:32 +00:00
config += "SSLCertificateKeyFile %s\n" % key[0]
return config
2015-03-10 21:51:10 +00:00
def get_security(self, directives):
2015-03-10 22:27:32 +00:00
config = ''
2015-03-10 21:51:10 +00:00
for rules in directives.get('sec_rule_remove', []):
2014-10-17 14:59:06 +00:00
for rule in rules.value.split():
2015-03-10 22:27:32 +00:00
config += "SecRuleRemoveById %i\n" % int(rule)
2015-03-12 14:05:23 +00:00
for modsecurity in directives.get('sec_engine', []):
2015-03-10 22:27:32 +00:00
config += textwrap.dedent("""\
2015-03-10 21:51:10 +00:00
<Location %s>
SecRuleEngine off
2015-03-12 14:05:23 +00:00
</Location>
2015-03-10 21:51:10 +00:00
""") % modsecurity
2015-03-10 22:27:32 +00:00
return config
2014-10-30 16:34:02 +00:00
2015-03-10 21:51:10 +00:00
def get_redirects(self, directives):
2015-03-10 22:27:32 +00:00
config = ''
2015-03-10 21:51:10 +00:00
for redirect in directives.get('redirect', []):
source, target = redirect.split()
if re.match(r'^.*[\^\*\$\?\)]+.*$', redirect):
2015-03-10 22:27:32 +00:00
config += "RedirectMatch %s %s\n" % (source, target)
2014-10-30 16:34:02 +00:00
else:
2015-03-10 22:27:32 +00:00
config += "Redirect %s %s\n" % (source, target)
return config
2014-05-08 16:59:35 +00:00
2015-03-10 21:51:10 +00:00
def get_proxies(self, directives):
2015-03-10 22:27:32 +00:00
config = ''
2015-03-10 21:51:10 +00:00
for proxy in directives.get('proxy', []):
2015-03-10 22:27:32 +00:00
source, target = proxy.split()
2015-03-10 21:51:10 +00:00
source = normurlpath(source)
2015-03-10 22:27:32 +00:00
config += 'ProxyPass %s %s\n' % (source, target)
config += 'ProxyPassReverse %s %s\n' % (source, target)
return config
2015-03-10 21:51:10 +00:00
# def get_protections(self, site):
# protections = ''
# context = self.get_context(site)
# for protection in site.directives.filter(name='directory_protection'):
# path, name, passwd = protection.value.split()
# path = os.path.join(context['root'], path)
# passwd = os.path.join(self.USER_HOME % context, passwd)
# 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)
# )
# return protections
2014-05-08 16:59:35 +00:00
def enable_or_disable(self, site):
context = self.get_context(site)
if site.is_active:
2015-03-02 10:37:25 +00:00
self.append(textwrap.dedent("""\
if [[ ! -f %(sites_enabled)s ]]; then
a2ensite %(site_unique_name)s.conf
UPDATED=1
fi""") % context
)
2014-05-08 16:59:35 +00:00
else:
2015-03-02 10:37:25 +00:00
self.append(textwrap.dedent("""\
if [[ -f %(sites_enabled)s ]]; then
a2dissite %(site_unique_name)s.conf;
UPDATED=1
fi""") % context
)
2014-05-08 16:59:35 +00:00
def get_username(self, site):
2015-03-10 21:51:10 +00:00
option = site.get_directives().get('user_group')
if option:
2015-03-10 21:51:10 +00:00
return option[0]
return site.account.username
def get_groupname(self, site):
2015-03-10 21:51:10 +00:00
option = site.get_directives().get('user_group')
if option and ' ' in option:
user, group = option.split()
return group
return site.account.username
2014-05-08 16:59:35 +00:00
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,
'user': self.get_username(site),
'group': self.get_groupname(site),
2015-03-02 10:37:25 +00:00
'sites_enabled': "%s.conf" % os.path.join(sites_enabled, site.unique_name),
2014-05-08 16:59:35 +00:00
'sites_available': "%s.conf" % os.path.join(sites_available, site.unique_name),
2015-03-02 10:37:25 +00:00
'access_log': site.get_www_access_log_path(),
'error_log': site.get_www_error_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,
2015-03-10 21:51:10 +00:00
'location': normurlpath(content.path),
2014-05-08 16:59:35 +00:00
'app_name': content.webapp.name,
'app_path': content.webapp.get_path(),
})
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):
super(Apache2Traffic, self).prepare()
2014-11-20 15:34:59 +00:00
ignore_hosts = '\\|'.join(settings.WEBSITES_TRAFFIC_IGNORE_HOSTS)
context = {
'current_date': self.current_date.strftime("%Y-%m-%d %H:%M:%S %Z"),
'ignore_hosts': '-v "%s"' % ignore_hosts if ignore_hosts else '',
}
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
2014-11-20 15:34:59 +00:00
INI_DATE=$(date "+%%Y%%m%%d%%H%%M%%S" -d "$2")
END_DATE=$(date '+%%Y%%m%%d%%H%%M%%S' -d '%(current_date)s')
2014-07-25 15:17:50 +00:00
LOG_FILE="$3"
{
2015-03-02 10:37:25 +00:00
{ grep %(ignore_hosts)s ${LOG_FILE} || echo -e '\\r'; } \\
2014-11-20 15:34:59 +00:00
| awk -v ini="${INI_DATE}" -v end="${END_DATE}" '
BEGIN {
sum = 0
2015-03-02 10:37:25 +00:00
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"
2014-11-20 15:34:59 +00:00
} {
# 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
}' || [[ $? == 1 ]] && true
2014-07-25 15:17:50 +00:00
} | xargs echo ${OBJECT_ID}
}""") % context)
2014-07-25 15:17:50 +00:00
2014-07-09 16:17:43 +00:00
def monitor(self, site):
context = self.get_context(site)
2014-11-20 15:34:59 +00:00
self.append('monitor {object_id} "{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
}