diff options
author | Stonewall Jackson <stonewall@sacredheartsc.com> | 2023-02-08 18:42:49 -0500 |
---|---|---|
committer | Stonewall Jackson <stonewall@sacredheartsc.com> | 2023-02-08 18:42:49 -0500 |
commit | 715a002f564ecaa757633ec66b191d84a045eceb (patch) | |
tree | b538c13c026e8f0f3a2335aaa02a0d8fada59237 /roles/apache_vhost | |
parent | 3c20895bc608c081eea9c1ddac097644ec79c3a5 (diff) | |
download | selfhosted-715a002f564ecaa757633ec66b191d84a045eceb.tar.gz selfhosted-715a002f564ecaa757633ec66b191d84a045eceb.zip |
document apache roles
Diffstat (limited to 'roles/apache_vhost')
-rw-r--r-- | roles/apache_vhost/README.md | 190 | ||||
-rw-r--r-- | roles/apache_vhost/defaults/main.yml | 6 | ||||
-rw-r--r-- | roles/apache_vhost/tasks/main.yml | 2 | ||||
-rw-r--r-- | roles/apache_vhost/templates/etc/httpd/conf.d/vhost.conf.j2 | 10 |
4 files changed, 200 insertions, 8 deletions
diff --git a/roles/apache_vhost/README.md b/roles/apache_vhost/README.md new file mode 100644 index 0000000..186ef4b --- /dev/null +++ b/roles/apache_vhost/README.md @@ -0,0 +1,190 @@ +Apache VirtualHost +================== + +Description +----------- + +The `apache_vhost` role retrieves HTTPS certificates and generates an Apache +configuration file for a given [VirtualHost](https://httpd.apache.org/docs/2.4/vhosts/examples.html). + + +Variables +--------- + +This role **accepts** the following variables: + +Variable | Default | Description +----------------------------|---------------------------------------------|------------ +`apache_server_name` | `{{ ansible_fqdn }}` | [ServerName](https://httpd.apache.org/docs/2.4/mod/core.html#servername) value +`apache_server_aliases` | `[]` if `apache_letsencrypt`, else `cnames` | [ServerAlias](https://httpd.apache.org/docs/2.4/mod/core.html#serveralias) values +`apache_config_name` | `{{ apache_server_name }}` | Name of config file in `/etc/httpd/conf.d` +`apache_listen` | `*` | Network interface for VirtualHost +`apache_default_vhost` | no | Make this VirtualHost the default if no other VirtualHosts match the request +`apache_document_root` | | Path to [DocumentRoot](https://httpd.apache.org/docs/2.4/mod/core.html#documentroot) +`apache_autoindex` | no | Automatically generate file listings +`apache_use_ssl` | yes | Enable HTTPS +`apache_letsencrypt` | no | Use LetsEncrypt (rather than FreeIPA) to acquire certificates +`apache_redirect_to_https` | yes | 301 redirect HTTP requests to HTTPS +`apache_use_http2` | yes | Enable HTTP2 protocol +`apache_canonical_hostname` | | 301 redirect all requests to this hostname +`apache_config` | | VirtualHost config block (see usage below) + +The `apache_config` block is interpolated into the VirtualHost configuration file +at the appropriate location, based on the values of `apache_redirect_to_https`, +and `apache_use_ssl`, etc. + +If `apache_document_root` is defined, and `apache_config` does not contain a +`<Directory>` block, a default one will be generated for you. + +This role **exports** the following variables for use in the `apache_config` block: + +Variable | Description +-------------------------------|------------ +`apache_ldap_url` | [AuthLDAPURL](https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html#authldapurl) value, based on FreeIPA LDAP servers and user base DN. +`apache_ldap_creds` | Config lines for [AuthLDAPBindDN](https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html#authldapbinddn) and [AuthLDAPBindPassword](https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html#authldapbindpassword) +`apache_ldap_config` | Config lines for [AuthLDAPUrl](https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html#authldapurl), [AuthLDAPBindDN](https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html#authldapbinddn), and [AuthLDAPBindPassword](https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html#authldapbindpassword) +`apache_gssapi_session_config` | Config lines for `mod_auth_gssapi` [session configuration](https://github.com/gssapi/mod_auth_gssapi#gssapiusesessions) +`apache_proxy_vhost_config` | Common `ProxyPass` configuration +`apache_proxy_header_config` | `RequestHeader` configuration for `ProxyPass` +`apache_proxy_config` | `apache_proxy_vhost_config` + `apache_proxy_header_config` (you should generally include this before any `ProxyPass` lines) + + +Usage +----- + +Example playbook: + +````yaml +- name: create a vhost without HTTPS + hosts: www1 + roles: + - role: apache_vhost + vars: + apache_server_name: nohttps.example.com + apache_document_root: /var/www/nohttps.example.com + apache_use_ssl: no + +- name: create a simple HTTPS vhost for www1.example.com + hosts: www2 + roles: + - role: apache_vhost + vars: + apache_document_root: /var/www/intranet.example.com + +- name: create a reverse proxy + hosts: www3 + roles: + - role: apache_vhost + vars: + apache_config: | + {{ apache_proxy_config }} + ProxyPass / http://127.0.0.1:8080/ + ProxyPassReverse / http://127.0.0.1:8080/ + +- name: create a reverse proxy with group-based authentication + hosts: www4 + roles: + - role: apache_vhost + vars: + apache_config: | + ProxyPass / http://127.0.0.1:8080/ + ProxyPassReverse / http://127.0.0.1:8080/ + {{ apache_proxy_config }} + + <Location /> + AuthName "FreeIPA Single Sign-On" + # Use GSSAPI for clients in the kerberized_cidrs network, LDAP basic auth otherwise. + <If "{% for cidr in kerberized_cidrs %}-R '{{ cidr }}'{% if not loop.last %} || {% endif %}{% endfor %}"> + AuthType GSSAPI + GssapiLocalName On + {{ apache_gssapi_session_config }} + </If> + <Else> + AuthType Basic + AuthBasicProvider ldap + </Else> + {{ apache_ldap_config }} + Require ldap-attribute memberof=cn=role-app-access,{{ freeipa_group_basedn }} + </Location> + +- name: create a vhost with a LetsEncrypt certificate + hosts: www5 + roles: + - role: apache_vhost + vars: + apache_letsencrypt: yes + apache_server_name: public.example.com + apache_server_aliases: + - www.example.com + - some-cname.example.com + apache_document_root: /var/www/public.example.com +```` + + +Authentication Performance +----------------------------------- + +To perform group-based authentication in Apache, the FreeIPA documentation [recommends](https://freeipa.readthedocs.io/en/latest/workshop/5-web-app-authnz.html#hbac-for-web-services) +using `mod_authnz_pam` along with a custom PAM service with a FreeIPA [HBAC](https://freeipa.readthedocs.io/en/latest/workshop/4-hbac.html) +rule. + +While this does work, the performance is absolutely abysmal. With the recommended +configuration, Apache invokes a full PAM session (along with associated SSSD queries) +for every single HTTP request. There appears to be no caching whatsoever. + +For simple HTML documents, I imagine this works well enough. But dynamic webapps +with many background HTTP calls are basically unusable. + +### mod\_authnz\_ldap + +`mod_authnz_ldap` provides robust caching out of the box, and supports group- +and attribute-based authorization. However, it lacks single sign-on capability +using GSSAPI--it's limited to HTTP Basic Auth. + +### mod\_auth\_gssapi + +`mod_auth_gssapi` provides single sign-on, and also supports session caching +using the `GssapiUseSessions` parameter for improved performance. + +Unfortunately, the session caching only applies to GSSAPI logins. If you use the +`GssapiBasicAuth` option to fall back to username/password authentication, no +caching will take place, and `httpd` will (essentially) perform a full `kinit` +for each HTTP request--again killing performance. + +### Solution + +The solution I came up with was to force GSSAPI authentication for certain client +subnets, and use `mod_authnz_ldap`'s Basic authentication for everything else (see +play #4 in the example above). This works for my environment because my Linux +workstations are all on dedicated subnets. + +If the client IP address matches a network in the `kerberized_cidrs` Ansible +variable, Apache uses GSSAPI authentication for the first request, and uses +Cookie-based session authentication thereafter. + +If the client IP address does *not* match a kerberized CIDR, then Basic Auth +is used via `mod_authnz_ldap`. Although Basic Auth is still performed for every +HTTP request, `mod_authnz_ldap` caches the result for each username/password +combination, so the added latency is negligible. + +In either case, any group-based authorization checks are performed by the +`mod_authnz_ldap` module. This is the only way I've found to get single sign-on +and group-based authorization working with acceptable latency in Apache. + + +ProxyPass with Unix Domain Sockets +---------------------------------- + +The syntax for creating a reverse proxy to a Unix domain socket is very +nonintuitive. It looks like this: + +````apache +ProxyPass unix:/path/to/socket.sock|http://foobar/ +ProxyPassReverse unix:/path/to/socket.sock|http://foobar/ +```` + +It doesn't matter what you choose for the `foobar` value, as long as it is +unique. If you have multiple `ProxyPass` directives for different locations +with the same `foobar` value, all requests will go to the same place! + +I still don't quite understand the purpose of this, but now you know. diff --git a/roles/apache_vhost/defaults/main.yml b/roles/apache_vhost/defaults/main.yml index c9bc05c..9a91522 100644 --- a/roles/apache_vhost/defaults/main.yml +++ b/roles/apache_vhost/defaults/main.yml @@ -2,6 +2,10 @@ apache_server_name: '{{ ansible_fqdn }}' apache_server_aliases: '{{ [] if apache_letsencrypt else cnames }}' apache_default_vhost: no +apache_config_name: '{{ apache_server_name }}' + +apache_listen: '*' + apache_autoindex: no apache_letsencrypt: no @@ -9,6 +13,4 @@ apache_use_ssl: yes apache_use_http2: yes apache_redirect_to_https: yes -apache_ldap_servers: '{{ freeipa_hosts }}' - apache_config: '' diff --git a/roles/apache_vhost/tasks/main.yml b/roles/apache_vhost/tasks/main.yml index ebe6fe6..29d08ab 100644 --- a/roles/apache_vhost/tasks/main.yml +++ b/roles/apache_vhost/tasks/main.yml @@ -12,7 +12,7 @@ - name: generate vhost configuration template: src: etc/httpd/conf.d/vhost.conf.j2 - dest: /etc/httpd/conf.d/vhost-{{ '000-default' if apache_default_vhost else (apache_config_name | default(apache_server_name)) }}.conf + dest: /etc/httpd/conf.d/vhost-{{ '000-default' if apache_default_vhost else apache_config_name }}.conf mode: 0640 lstrip_blocks: yes notify: reload apache diff --git a/roles/apache_vhost/templates/etc/httpd/conf.d/vhost.conf.j2 b/roles/apache_vhost/templates/etc/httpd/conf.d/vhost.conf.j2 index a925372..12a682e 100644 --- a/roles/apache_vhost/templates/etc/httpd/conf.d/vhost.conf.j2 +++ b/roles/apache_vhost/templates/etc/httpd/conf.d/vhost.conf.j2 @@ -1,5 +1,5 @@ {% if apache_use_ssl and apache_redirect_to_https %} -<VirtualHost {{ apache_listen | default('*') }}:80> +<VirtualHost {{ apache_listen }}:80> ServerName {{ apache_server_name }} {% for alias in apache_server_aliases %} ServerAlias {{ alias }} @@ -18,7 +18,7 @@ {% else %} {% if apache_canonical_hostname is defined and (apache_server_aliases | length > 0) %} -<VirtualHost {{ apache_listen | default('*') }}:80> +<VirtualHost {{ apache_listen }}:80> {% for alias in ([apache_server_name] + apache_server_aliases) | reject('equalto', apache_canonical_hostname) | list %} {% if loop.first %} ServerName {{ alias }} @@ -36,7 +36,7 @@ </VirtualHost> {% endif %} -<VirtualHost {{ apache_listen | default('*') }}:80> +<VirtualHost {{ apache_listen }}:80> {% if apache_document_root is defined %} DocumentRoot "{{ apache_document_root }}" {% endif %} @@ -73,7 +73,7 @@ {% if apache_use_ssl %} {% if apache_canonical_hostname is defined and (apache_server_aliases | length > 0) %} -<VirtualHost {{ apache_listen | default('*') }}:443> +<VirtualHost {{ apache_listen }}:443> {% for alias in ([apache_server_name] + apache_server_aliases) | reject('equalto', apache_canonical_hostname) | list %} {% if loop.first %} ServerName {{ alias }} @@ -96,7 +96,7 @@ </VirtualHost> {% endif %} -<VirtualHost {{ apache_listen | default('*') }}:443> +<VirtualHost {{ apache_listen }}:443> {% if apache_document_root is defined %} DocumentRoot "{{ apache_document_root }}" {% endif %} |