From 0261e875679f1bf63c8d689da7fc7e014597885d Mon Sep 17 00:00:00 2001 From: Stonewall Jackson Date: Sat, 4 Feb 2023 01:23:43 -0500 Subject: initial commit --- roles/mediawiki/defaults/main.yml | 52 ++++ roles/mediawiki/files/var/www/mediawiki/robots.txt | 2 + roles/mediawiki/meta/main.yml | 8 + roles/mediawiki/tasks/database.yml | 50 ++++ roles/mediawiki/tasks/extension.yml | 12 + roles/mediawiki/tasks/freeipa.yml | 40 +++ roles/mediawiki/tasks/main.yml | 134 ++++++++++ .../var/www/mediawiki/LocalSettings.php.j2 | 288 +++++++++++++++++++++ roles/mediawiki/vars/main.yml | 125 +++++++++ 9 files changed, 711 insertions(+) create mode 100644 roles/mediawiki/defaults/main.yml create mode 100644 roles/mediawiki/files/var/www/mediawiki/robots.txt create mode 100644 roles/mediawiki/meta/main.yml create mode 100644 roles/mediawiki/tasks/database.yml create mode 100644 roles/mediawiki/tasks/extension.yml create mode 100644 roles/mediawiki/tasks/freeipa.yml create mode 100644 roles/mediawiki/tasks/main.yml create mode 100644 roles/mediawiki/templates/var/www/mediawiki/LocalSettings.php.j2 create mode 100644 roles/mediawiki/vars/main.yml (limited to 'roles/mediawiki') diff --git a/roles/mediawiki/defaults/main.yml b/roles/mediawiki/defaults/main.yml new file mode 100644 index 0000000..4b65d70 --- /dev/null +++ b/roles/mediawiki/defaults/main.yml @@ -0,0 +1,52 @@ +mediawiki_version: 1.39.1 +mediawiki_extension_version: REL1_39 + +mediawiki_kerberized_cidrs: '{{ kerberized_cidrs }}' + +mediawiki_user: s-mediawiki +mediawiki_db_name: mediawiki +mediawiki_db_host: '{{ postgresql_host }}' + +mediawiki_access_group: role-wiki-access +mediawiki_admin_group: role-wiki-admin + +mediawiki_max_upload_size: 50M +mediawiki_max_upload_count: 32 + +mediawiki_custom_namespaces: [] + +mediawiki_use_subpages: true + +mediawiki_ldap_servers: '{{ freeipa_hosts }}' +mediawiki_sysaccount_username: mediawiki + +mediawiki_site_name: '{{ organization }} Wiki' +mediawiki_meta_namespace: "{{ organization | regex_replace('\\s*', '') }}" +mediawiki_fqdn: '{{ ansible_fqdn }}' +mediawiki_url: https://{{ mediawiki_fqdn }} + +mediawiki_admin_username: admin +mediawiki_emergency_contact: root@{{ email_domain }} +mediawiki_password_sender: wiki-noreply@{{ email_domain }} +mediawiki_email_authentication: no + +mediawiki_local_timezone: '{{ timezone }}' +mediawiki_language_code: en + +mediawiki_default_skin: vector +mediawiki_default_mobile_skin: minerva + +mediawiki_disable_anonymous_read: no +mediawiki_disable_anonymous_edit: yes + +mediawiki_block_wan_login: yes + +mediawiki_apc_shm_size: 256M + +mediawiki_skins: + - Vector + - MinervaNeue + +# mediawiki_logo_1x: /path/to/1x/logo.jpg +# mediawiki_logo_icon: /path/to/icon/logo.jpg +# mediawiki_logo_favicon: /path/to/favicon.ico diff --git a/roles/mediawiki/files/var/www/mediawiki/robots.txt b/roles/mediawiki/files/var/www/mediawiki/robots.txt new file mode 100644 index 0000000..c218f6f --- /dev/null +++ b/roles/mediawiki/files/var/www/mediawiki/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /index.php? diff --git a/roles/mediawiki/meta/main.yml b/roles/mediawiki/meta/main.yml new file mode 100644 index 0000000..f0e6864 --- /dev/null +++ b/roles/mediawiki/meta/main.yml @@ -0,0 +1,8 @@ +dependencies: + - role: yum + yum_repositories: epel + tags: yum + + - role: freeipa_system_account + system_account_username: '{{ mediawiki_sysaccount_username }}' + system_account_password: '{{ mediawiki_sysaccount_password }}' diff --git a/roles/mediawiki/tasks/database.yml b/roles/mediawiki/tasks/database.yml new file mode 100644 index 0000000..b00a8a1 --- /dev/null +++ b/roles/mediawiki/tasks/database.yml @@ -0,0 +1,50 @@ +- name: create postgresql database + postgresql_db: + name: '{{ mediawiki_db_name }}' + state: present + delegate_to: "{{ postgresql_host.split('.')[0] }}" + become: True + become_user: postgres + +- name: create postgresql user + postgresql_user: + name: '{{ mediawiki_user }}' + db: '{{ mediawiki_db_name }}' + priv: ALL + state: present + delegate_to: "{{ postgresql_host.split('.')[0] }}" + become: True + become_user: postgres + +- name: check if database schema is initialized + postgresql_query: + login_user: '{{ mediawiki_user }}' + login_host: '{{ mediawiki_db_host }}' + db: '{{ mediawiki_db_name }}' + query: SELECT 1 FROM mediawiki.page + become: True + become_user: apache + environment: + GSS_USE_PROXY: 'yes' + register: mediawiki_check_db + failed_when: false + +- name: initialize database schema + command: > + php {{ mediawiki_home }}/maintenance/install.php + --server {{ mediawiki_url }} + --dbuser {{ mediawiki_user }} + --dbname {{ mediawiki_db_name }} + --dbserver {{ mediawiki_db_host }} + --dbtype postgres + --pass {{ mediawiki_admin_password | quote }} + --scriptpath / + {{ mediawiki_site_name | quote }} + {{ mediawiki_admin_username }} + become: True + become_user: apache + environment: + GSS_USE_PROXY: 'yes' + when: + - mediawiki_check_db.msg is defined + - mediawiki_check_db.msg is search('relation "mediawiki.page" does not exist') diff --git a/roles/mediawiki/tasks/extension.yml b/roles/mediawiki/tasks/extension.yml new file mode 100644 index 0000000..02f5dc3 --- /dev/null +++ b/roles/mediawiki/tasks/extension.yml @@ -0,0 +1,12 @@ +- name: get url for extension tarball + uri: + url: 'https://www.mediawiki.org/w/index.php?title=Special:ExtensionDistributor&extdistname={{ extension_name }}&extdistversion={{ extension_version }}' + register: extension_distributor_resp + +- name: extract extension tarball + unarchive: + src: "{{ extension_distributor_resp.refresh.split(';') | map('trim') | select('search', '^url=') | first | regex_replace('^url=', '') }}" + remote_src: yes + dest: '{{ mediawiki_home }}/extensions' + owner: apache + group: apache diff --git a/roles/mediawiki/tasks/freeipa.yml b/roles/mediawiki/tasks/freeipa.yml new file mode 100644 index 0000000..565cdca --- /dev/null +++ b/roles/mediawiki/tasks/freeipa.yml @@ -0,0 +1,40 @@ +- name: create mediawiki user + ipauser: + ipaadmin_principal: '{{ ipa_user }}' + ipaadmin_password: '{{ ipa_pass }}' + name: '{{ mediawiki_user }}' + loginshell: /sbin/nologin + homedir: '{{ mediawiki_home }}' + givenname: MediaWiki + sn: Service Account + state: present + run_once: True + +- name: create mediawiki groups + ipagroup: + ipaadmin_principal: '{{ ipa_user }}' + ipaadmin_password: '{{ ipa_pass }}' + name: '{{ item }}' + nonposix: yes + state: present + run_once: True + loop: + - '{{ mediawiki_access_group }}' + - '{{ mediawiki_admin_group }}' + +- name: retrieve mediawiki user keytab + include_role: + name: freeipa_keytab + vars: + keytab_principal: '{{ mediawiki_user }}' + keytab_path: '{{ mediawiki_keytab }}' + +- name: configure gssproxy for kerberized postgres + include_role: + name: gssproxy_client + vars: + gssproxy_name: mediawiki + gssproxy_section: service/php-fpm + gssproxy_client_keytab: '{{ mediawiki_keytab }}' + gssproxy_cred_usage: initiate + gssproxy_euid: apache diff --git a/roles/mediawiki/tasks/main.yml b/roles/mediawiki/tasks/main.yml new file mode 100644 index 0000000..d0c3820 --- /dev/null +++ b/roles/mediawiki/tasks/main.yml @@ -0,0 +1,134 @@ +- name: install packages + dnf: + name: '{{ mediawiki_packages }}' + state: present + +- name: set PHP APC cache size + lineinfile: + path: /etc/php.d/40-apcu.ini + regexp: ^apc\.shm_size= + line: apc.shm_size={{ mediawiki_apc_shm_size }} + state: present + notify: restart php-fpm + +- import_tasks: freeipa.yml + tags: freeipa + +- name: create mediawiki webroot + file: + path: '{{ mediawiki_home }}' + state: directory + +- name: get current mediawiki version + command: php {{ mediawiki_home }}/maintenance/version.php + become: True + become_user: apache + environment: + GSS_USE_PROXY: 'yes' + changed_when: no + failed_when: no + register: mediawiki_current_version + +- name: extract mediawiki tarball + unarchive: + src: '{{ mediawiki_tarball }}' + remote_src: yes + dest: '{{ mediawiki_home }}' + owner: apache + group: apache + extra_opts: + - '--strip-components=1' + +- name: set permissions on writeable directories + file: + path: '{{ mediawiki_home }}/{{ item }}' + state: directory + mode: 0770 + owner: apache + group: apache + setype: _default + loop: '{{ mediawiki_writable_dirs }}' + +- name: set selinux context for writeable directories + sefcontext: + target: '{{ mediawiki_home }}/{{ item }}(/.*)?' + setype: httpd_sys_rw_content_t + state: present + loop: '{{ mediawiki_writable_dirs }}' + register: mediawiki_writeable_sefcontext + tags: selinux + +- name: apply selinux context to writeable directories + command: 'restorecon -R {{ mediawiki_home }}/{{ item }}' + when: mediawiki_writeable_sefcontext.results[index].changed + loop: '{{ mediawiki_writable_dirs }}' + loop_control: + index_var: index + tags: selinux + +- name: set selinux context for executable directories + sefcontext: + target: '{{ mediawiki_home }}/{{ item }}(/.*)?' + setype: httpd_sys_script_exec_t + state: present + loop: '{{ mediawiki_executable_dirs }}' + register: mediawiki_executable_sefcontext + tags: selinux + +- name: apply selinux context to executable directories + command: 'restorecon -R {{ mediawiki_home }}/{{ item }}' + when: mediawiki_executable_sefcontext.results[index].changed + loop: '{{ mediawiki_executable_dirs }}' + loop_control: + index_var: index + tags: selinux + +- import_tasks: database.yml + tags: database + +- name: generate LocalSettings.php + template: + src: '{{ mediawiki_home[1:] }}/LocalSettings.php.j2' + dest: '{{ mediawiki_home }}/LocalSettings.php' + owner: root + group: apache + mode: 0640 + register: mediawiki_localsettings + +- name: install extensions + include_tasks: extension.yml + vars: + extension_name: '{{ item if item is string else item.name }}' + extension_version: '{{ mediawiki_extension_version if item is string else (item.version | default(mediawiki_extension_version)) }}' + loop: '{{ mediawiki_extensions }}' + +- name: update database schema + command: php {{ mediawiki_home }}/maintenance/update.php --quick + become: yes + become_user: apache + environment: + GSS_USE_PROXY: 'yes' + when: mediawiki_localsettings.changed or (mediawiki_current_version.rc == 0 and not mediawiki_current_version.stdout is search(mediawiki_version)) + +- name: copy robots.txt + copy: + src: '{{ mediawiki_home[1:] }}/robots.txt' + dest: '{{ mediawiki_home }}/robots.txt' + +- name: copy 1x logo + copy: + src: '{{ mediawiki_logo_1x }}' + dest: '{{ mediawiki_home }}/resources/assets/{{ mediawiki_logo_1x | basename }}' + when: mediawiki_logo_1x is defined + +- name: copy icon logo + copy: + src: '{{ mediawiki_logo_icon }}' + dest: '{{ mediawiki_home }}/resources/assets/{{ mediawiki_logo_icon | basename }}' + when: mediawiki_logo_icon is defined + +- name: copy favicon + copy: + src: '{{ mediawiki_favicon }}' + dest: '{{ mediawiki_home }}/resources/assets/{{ mediawiki_favicon | basename }}' + when: mediawiki_favicon is defined diff --git a/roles/mediawiki/templates/var/www/mediawiki/LocalSettings.php.j2 b/roles/mediawiki/templates/var/www/mediawiki/LocalSettings.php.j2 new file mode 100644 index 0000000..e94ca80 --- /dev/null +++ b/roles/mediawiki/templates/var/www/mediawiki/LocalSettings.php.j2 @@ -0,0 +1,288 @@ + [ + 'connection' => [ + 'server' => '{{ mediawiki_ldap_servers | join(' ') }}', + 'user' => 'uid={{ mediawiki_sysaccount_username }},{{ freeipa_sysaccount_basedn }}', + 'pass' => '{{ mediawiki_sysaccount_password }}', + 'enctype' => 'tls', + 'options' => [ + 'LDAP_OPT_DEREF' => 1 + ], + 'basedn' => '{{ freeipa_basedn }}', + 'groupbasedn' => '{{ freeipa_group_basedn }}', + 'grouprequest' => 'MediaWiki\\Extension\\LDAPProvider\\UserGroupsRequest\\UserMemberOf::factory', + 'presearchusernamemodifiers' => [ 'lowercase' ], + 'userbasedn' => '{{ freeipa_user_basedn }}', + 'searchattribute' => 'uid', + 'searchstring' => 'uid=USER-NAME,{{ freeipa_user_basedn }}', + 'usernameattribute' => 'uid', + 'realnameattribute' => 'cn', + 'emailattribute' => 'mail' + ], + 'groupsync' => [ + 'mechanism' => 'mappedgroups', + 'mapping' => [ +{% for group in mediawiki_custom_namespaces + | selectattr('restrict', 'defined') + | map(attribute='restrict') + | map('dict2items') + | flatten + | map(attribute='value') + | unique + | difference(mediawiki_builtin_groups) %} + '{{ group }}' => 'cn={{ group }},{{ freeipa_group_basedn }}', +{% endfor %} + 'sysop' => 'cn={{ mediawiki_admin_group }},{{ freeipa_group_basedn }}', + 'interface-admin' => 'cn={{ mediawiki_admin_group }},{{ freeipa_group_basedn }}', + 'bureaucrat' => 'cn={{ mediawiki_admin_group }},{{ freeipa_group_basedn }}' + ] + ], + 'userinfo' => [ + 'attributes-map' => [ + 'email' => 'mail', + 'realname' => 'cn' + ] + ], + 'authorization' => [ + 'rules' => [ + 'groups' => [ + 'required' => [ + 'cn={{ mediawiki_access_group }},{{ freeipa_group_basedn }}', + 'cn={{ mediawiki_admin_group }},{{ freeipa_group_basedn }}' + ] + ] + ] + ] + ] + ]; + + return new \MediaWiki\Extension\LDAPProvider\DomainConfigProvider\InlinePHPArray( $config ); +}; + + +### Extension: PluggableAuth +$wgPluggableAuth_ButtonLabel = 'Log In'; + + +### Extension: CodeMirror +$wgDefaultUserOptions['usecodemirror'] = 1; +$wgCodeMirrorEnableBracketMatching = true; +$wgCodeMirrorLineNumberingNamespaces = null; + + +### Extension: UploadWizard +$wgUploadNavigationUrl = '/Special:UploadWizard'; + + +### Extension: Auth_remoteuser +$wgAuthRemoteuserUserNameReplaceFilter = [ + '@{{ freeipa_realm }}$' => '' +]; + + +### Extension: Lockdown +{% for ns in mediawiki_custom_namespaces | selectattr('restrict', 'defined') %} +{% for r in ns.restrict | dict2items(key_name='perm', value_name='group') %} +$wgNamespacePermissionLockdown[{{ ns.id }}]['{{ r.perm }}'] = {{ ([r.group] if r.group is string else r.group) | to_json }}; +$wgNamespacePermissionLockdown[{{ ns.talk_id }}]['{{ r.perm }}'] = {{ ([r.group] if r.group is string else r.group) | to_json }}; +{% endfor %} +$wgNonincludableNamespaces[] = {{ ns.id }}; +$wgNonincludableNamespaces[] = {{ ns.talk_id }}; +{% endfor %} + + +### Extension: VisualEditor +$wgVisualEditorAvailableNamespaces = [ +{% for ns in mediawiki_custom_namespaces %} + '{{ ns.namespace }}' => true, + '{{ ns.namespace }}Talk' => true{% if not loop.last %},{% endif %} + +{% endfor %} +]; diff --git a/roles/mediawiki/vars/main.yml b/roles/mediawiki/vars/main.yml new file mode 100644 index 0000000..d82f2f4 --- /dev/null +++ b/roles/mediawiki/vars/main.yml @@ -0,0 +1,125 @@ +mediawiki_tarball: https://releases.wikimedia.org/mediawiki/{{ mediawiki_version | splitext | first }}/mediawiki-{{ mediawiki_version }}.tar.gz +mediawiki_home: /var/www/mediawiki +mediawiki_keytab: /var/lib/gssproxy/clients/{{ mediawiki_user }}.keytab + +mediawiki_packages: + - php + - php-json + - php-ldap + - php-mbstring + - php-opcache + - php-pdo + - php-pgsql + - php-xml + - php-intl + - php-gd + - php-pecl-apcu + - php-pecl-igbinary + - python3-psycopg2 + - python3 + - ImageMagick + - poppler-utils + - ghostscript + - varnish + +mediawiki_php_environment: + GSS_USE_PROXY: 'yes' + +mediawiki_php_admin_values: + post_max_size: '{{ mediawiki_max_upload_size }}' + upload_max_filesize: '{{ mediawiki_max_upload_size }}' + max_file_uploads: '{{ mediawiki_max_upload_count }}' + +mediawiki_writable_dirs: + - images + - cache + +mediawiki_executable_dirs: + - extensions/SyntaxHighlight_GeSHi/pygments + +mediawiki_builtin_extensions: + - WikiEditor + - VisualEditor + - MobileFrontend + - MultimediaViewer + - Math + - PageImages + - SyntaxHighlight_GeSHi + - PdfHandler + +mediawiki_extensions: + - PluggableAuth + - LDAPAuthorization + - LDAPAuthentication2 + - LDAPProvider + - MobileFrontend + - LDAPGroups + - LDAPUserInfo + - Auth_remoteuser + - CodeMirror + - RelatedArticles + - UploadWizard + - Lockdown + +mediawiki_builtin_groups: + - user + - autoconfirmed + - bot + - sysop + - interface-admin + - bureaucrat + - suppress + +mediawiki_apache_config: | + AllowEncodedSlashes NoDecode + + RewriteEngine On + + RewriteCond %{REQUEST_URI} ^/({{ mediawiki_rewrite_blacklist | map("regex_escape") | join("|") }})$ + RewriteRule ^(.*)$ %{DOCUMENT_ROOT}/index.php [L] + + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !\.php/ + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d + RewriteRule ^(.*)$ %{DOCUMENT_ROOT}/index.php [L] + + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !\.php/ + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d + RewriteRule ^(.*)/([a-z]*)$ %{DOCUMENT_ROOT}/index.php [L,QSA] + + + AuthName "FreeIPA Single Sign-On" + AuthType GSSAPI + + {{ apache_gssapi_session_config }} + Require valid-user + + + + + AllowOverride None + Require all denied + + +# Since we're using pretty URLs, page titles can clash with real files in the +# mediawiki directory. If this ever happens, add the file path to this list. +mediawiki_rewrite_blacklist: + - CODE_OF_CONDUCT.md + - COPYING + - CREDITS + - FAQ + - HISTORY + - INSTALL + - README.md + - SECURITY + - UPGRADE + - composer.json + - jsduck.json + +mediawiki_archive_shell: >- + TIMESTAMP=$(date +%Y%m%d%H%M%S); + tar czf "mediawiki-${TIMESTAMP}.tar.gz" + --transform "s|^\.|mediawiki-${TIMESTAMP}|" + -C "{{ mediawiki_home }}" + images -- cgit