Update to 2021-12-01 19:13

This commit is contained in:
Daniel Berteaud
2021-12-01 19:13:34 +01:00
commit 4c4556c660
2153 changed files with 60999 additions and 0 deletions

1
roles/zimbra/amavis.yml Normal file
View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,79 @@
---
zcs_install: False
zcs_version: 8.8.12
zcs_archive_name: zcs-8.8.12_GA_3794.RHEL7_64.20190329045002.tgz
zcs_archive_url: https://files.zimbra.com/downloads/{{ zcs_version }}_GA/{{ zcs_archive_name }}
zcs_archive_sha1: 9b1e5a13de311aab106953e321fc04970bfd3730
# Primary LDAP server of the cluster
# zcs_primary_ldap: ldap1.example.org
# Zimbra components to install
zcs_components: []
# - ldap
# - logger
# - mta
# - store
# - spell
# - memcached
# - proxy
zcs_main_domain: "{{ ansible_domain }}"
zcs_cluster_ip: []
zcs_smtp_src_ip:
- 0.0.0.0/0
zcs_http_src_ip:
- 0.0.0.0/0
# Additional list of IP able to access ldap services (zcs_cluster_ip are already allowed)
zcs_ldap_src_ip: []
zcs_clients_src_ip:
- 0.0.0.0/0
zcs_admin_src_ip: []
zcs_mysql_src_ip: []
# If we should get certificate from Let's Encrypt
# the letsencrypt role should be deployed also on the primary LDAP server
# Note that the certificate requested will have all the cluster members as alt names
# so be sure all the challenges can be resolved
zcs_letsencrypt: False
# The LDAP admin password (used for other servers to join the cluster for example
# zcs_ldap_admin_pass:
# Domain and their configuration. Used to sync LDAP with Zimbra
zcs_domains: {}
# zcs_domains:
# fws.fr:
# public_url: https://zm.fws.fr
# admin_url: https://zm.fws.fr:9071
# ldapsync:
# ldap:
# servers:
# - ldap://dc1.fws.fr:389
# schema: ad
# bind_dn: CN=Zimbra,OU=Apps,DC=fws,DC=fr
# bind_pass: s4cr3t.
# users:
# base: OU=People,DC=fws,DC=fr
# filter: (mail=*)
# groups:
# base: OU=Groups,DC=fws,DC=fr
# cas:
# enabled: False
# server_url: https://sso.fws.fr/cas
zcs_domain_defaults:
cas:
enabled: False
# Additional libs needed for CAS
zcs_cas_libs:
- url: https://repo1.maven.org/maven2/org/jasig/cas/client/cas-client-core/3.6.2/cas-client-core-3.6.2.jar
sha1: ccb636b9b8d8c048b4dd14b0b0627350def5e3a2
- url: https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.32/slf4j-api-1.7.32.jar
sha1: cdcff33940d9f2de763bc41ea05a0be5941176c3
# If defined, will add an always_bcc directive on MTA servers
# zcs_always_bcc: maillog@example.org

View File

@@ -0,0 +1,217 @@
#!/usr/bin/perl
#
# ***** BEGIN LICENSE BLOCK *****
# Zimbra Collaboration Suite Server
# Copyright (C) 2008, 2009, 2010, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software Foundation,
# version 2 of the License.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>.
# ***** END LICENSE BLOCK *****
use strict;
use lib '/opt/zimbra/common/lib/perl5';
use Sys::Syslog qw(:DEFAULT setlogsock);
use Net::LDAPapi;
use XML::Simple;
#
# Syslogging options for verbose mode and for fatal errors.
# NOTE: comment out the $syslog_socktype line if syslogging does not
# work on your system.
#
my $syslog_socktype = 'unix';
my $syslog_facility="mail";
my $syslog_options="pid";
our $syslog_priority="info";
our ($verbose, %attr, @ldap_url, $ldap_starttls_supported, $postfix_pw, $zimbra_pw, $delim_re);
my ($option, $action, $ldap_url, @val);
$ENV{'HOME'}='/opt/zimbra';
setlogsock $syslog_socktype;
openlog $0, $syslog_options, $syslog_facility;
my $localxml = XMLin("/opt/zimbra/conf/localconfig.xml");
$ldap_starttls_supported = $localxml->{key}->{ldap_starttls_supported}->{value};
chomp ($ldap_starttls_supported);
$postfix_pw = $localxml->{key}->{ldap_postfix_password}->{value};
chomp($postfix_pw);
$zimbra_pw = $localxml->{key}->{zimbra_ldap_password}->{value};
chomp($zimbra_pw);
$ldap_url = $localxml->{key}->{ldap_url}->{value};
chomp($ldap_url);
@ldap_url = split / /, $ldap_url;
sub smtpd_access_policy {
my($domain, $ldap, $mesg, $user, $canon_user, $daddr, @attrs, $result);
$daddr = lc $attr{recipient};
($user, $domain) = split /\@/, lc $attr{recipient};
$canon_user = (defined $delim_re) ? (split /$delim_re/, $user)[0] : $user;
syslog $syslog_priority, "Recipient Domain: %s", $domain if $verbose;
syslog $syslog_priority, "Recipient userid: %s", $user if $verbose;
foreach my $url (@ldap_url) {
$ldap=Net::LDAPapi->new(-url=>$url);
if ( $ldap_starttls_supported ) {
$mesg = $ldap->start_tls_s();
if ($mesg != 0) {
next;
}
}
$mesg = $ldap->bind_s("uid=zmpostfix,cn=appaccts,cn=zimbra",$postfix_pw);
if ($mesg != 0) {
next;
} else {
last;
}
}
if ($mesg != 0) {
syslog $syslog_priority, "Error: zmpostfixpolicyd unable to find working LDAP server";
return "dunno";
}
@attrs=('zimbraDomainType', 'zimbraMailCatchAllForwardingAddress');
$mesg = $ldap->search_s(
"",
LDAP_SCOPE_SUBTREE,
"(&(zimbraDomainName=$domain)(objectClass=zimbraDomain))",
\@attrs,
0,
$result
);
my $ent = $ldap->first_entry();
if ($ent != 0) {
if (lc(($ldap->get_values("zimbraDomainType"))[0]) eq "alias") {
my $robject = ($ldap->get_values("zimbraMailCatchAllForwardingAddress"))[0];
syslog $syslog_priority, "Real Domain: %s", $robject if $verbose;
@attrs=('1.1');
$mesg = $ldap->search_s(
"",
LDAP_SCOPE_SUBTREE,
"(&(|(zimbraMailDeliveryAddress=$user"."$robject)(zimbraMailDeliveryAddress=$canon_user"."$robject)".
"(zimbraMailDeliveryAddress=$daddr)(zimbraMailAlias=$user"."$robject)(zimbraMailAlias=$canon_user"."$robject)".
"(zimbraMailAlias=$daddr)(zimbraMailCatchAllAddress=$user"."$robject)(zimbraMailCatchAllAddress=$robject)".
"(zimbraMailCatchAllAddress=$daddr))(zimbraMailStatus=enabled))",
\@attrs,
0,
$result
);
$ent = $ldap->first_entry();
$ldap->unbind;
if ($ent != 0) {
return "dunno";
} else {
return "reject 5.1.1 Mailbox unavailable";
}
} else {
$ldap->unbind;
return "dunno";
}
}
$ldap->unbind;
return "dunno";
}
#
# Log an error and abort.
#
sub fatal_exit {
my($first) = shift(@_);
syslog "err", "fatal: $first", @_;
exit 1;
}
#
# We don't need getopt() for now.
#
while ($option = shift(@ARGV)) {
if ($option eq "-v") {
$verbose = 1;
} else {
syslog $syslog_priority, "Invalid option: %s. Usage: %s [-v]",
$option, $0;
exit 1;
}
}
#
# Unbuffer standard output.
#
select((select(STDOUT), $| = 1)[0]);
# Try to get recipient delimiter, if defined
# This will allow checking for valid recipient on alias domains
# even for recipient using delimiter. Eg user+foobar@alias.example.org
# will correctly check if user@example.org is valid
my ($ldap, $mesg, @attrs, $result);
foreach my $url (@ldap_url) {
$ldap=Net::LDAPapi->new(-url=>$url);
if ( $ldap_starttls_supported ) {
$mesg = $ldap->start_tls_s();
if ($mesg != 0) {
next;
}
}
$mesg = $ldap->bind_s("uid=zimbra,cn=admins,cn=zimbra",$zimbra_pw);
if ($mesg != 0) {
next;
} else {
last;
}
}
if ($mesg == 0){
@attrs=('zimbraMtaRecipientDelimiter');
$mesg = $ldap->search_s(
"",
LDAP_SCOPE_SUBTREE,
"(&(cn=config)(objectClass=zimbraGlobalConfig))",
\@attrs,
0,
$result
);
my $ent = $ldap->first_entry();
if ($ent != 0){
my $delim = ($ldap->get_values('zimbraMtaRecipientDelimiter'))[0];
if ($delim ne ''){
$delim_re = qr{[$delim]};
syslog $syslog_priority, "Recipient delimiter regex is $delim_re" if $verbose;
} else {
syslog $syslog_priority, "Recipient delimiter is an empty string so it won't be used" if $verbose;
}
} else {
syslog $syslog_priority, "Recipient delimiter not found" if $verbose;
}
# Unbind, everything else will bind with the postfix LDAP user
$ldap->unbind;
} else {
syslog $syslog_priority, "Couldn't bind with zimbra account, recipient delimiter won't be used" if $verbose;
}
#
# Receive a bunch of attributes, evaluate the policy, send the result.
#
while (<STDIN>) {
if (/([^=]+)=(.*)\n/) {
$attr{substr($1, 0, 512)} = substr($2, 0, 512);
} elsif ($_ eq "\n") {
if ($verbose) {
for (keys %attr) {
syslog $syslog_priority, "Attribute: %s=%s", $_, $attr{$_};
}
}
fatal_exit "unrecognized request type: '%s'", $attr{"request"}
unless $attr{"request"} eq "smtpd_access_policy";
$action = smtpd_access_policy();
syslog $syslog_priority, "Action: %s", $action if $verbose;
print STDOUT "action=$action\n\n";
%attr = ();
} else {
chop;
syslog $syslog_priority, "warning: ignoring garbage: %.100s", $_;
}
}

View File

@@ -0,0 +1,97 @@
--- /opt/zimbra/libexec/zmpostfixpolicyd.bak 2019-07-18 21:24:39.000000000 +0200
+++ /opt/zimbra/libexec/zmpostfixpolicyd 2020-11-23 10:02:37.956775250 +0100
@@ -30,7 +30,7 @@
my $syslog_facility="mail";
my $syslog_options="pid";
our $syslog_priority="info";
-our ($verbose, %attr, @ldap_url, $ldap_starttls_supported, $postfix_pw);
+our ($verbose, %attr, @ldap_url, $ldap_starttls_supported, $postfix_pw, $zimbra_pw, $delim_re);
my ($option, $action, $ldap_url, @val);
$ENV{'HOME'}='/opt/zimbra';
@@ -43,14 +43,17 @@
chomp ($ldap_starttls_supported);
$postfix_pw = $localxml->{key}->{ldap_postfix_password}->{value};
chomp($postfix_pw);
+$zimbra_pw = $localxml->{key}->{zimbra_ldap_password}->{value};
+chomp($zimbra_pw);
$ldap_url = $localxml->{key}->{ldap_url}->{value};
chomp($ldap_url);
@ldap_url = split / /, $ldap_url;
sub smtpd_access_policy {
- my($domain, $ldap, $mesg, $user, $daddr, @attrs, $result);
+ my($domain, $ldap, $mesg, $user, $canon_user, $daddr, @attrs, $result);
$daddr = lc $attr{recipient};
($user, $domain) = split /\@/, lc $attr{recipient};
+ $canon_user = (defined $delim_re) ? (split /$delim_re/, $user)[0] : $user;
syslog $syslog_priority, "Recipient Domain: %s", $domain if $verbose;
syslog $syslog_priority, "Recipient userid: %s", $user if $verbose;
foreach my $url (@ldap_url) {
@@ -90,8 +93,9 @@
$mesg = $ldap->search_s(
"",
LDAP_SCOPE_SUBTREE,
- "(&(|(zimbraMailDeliveryAddress=$user"."$robject)(zimbraMailDeliveryAddress=$daddr)(zimbraMailAlias=$user".
- "$robject)(zimbraMailAlias=$daddr)(zimbraMailCatchAllAddress=$user"."$robject)(zimbraMailCatchAllAddress=$robject)".
+ "(&(|(zimbraMailDeliveryAddress=$user"."$robject)(zimbraMailDeliveryAddress=$canon_user"."$robject)".
+ "(zimbraMailDeliveryAddress=$daddr)(zimbraMailAlias=$user"."$robject)(zimbraMailAlias=$canon_user"."$robject)".
+ "(zimbraMailAlias=$daddr)(zimbraMailCatchAllAddress=$user"."$robject)(zimbraMailCatchAllAddress=$robject)".
"(zimbraMailCatchAllAddress=$daddr))(zimbraMailStatus=enabled))",
\@attrs,
0,
@@ -140,6 +144,54 @@
#
select((select(STDOUT), $| = 1)[0]);
+# Try to get recipient delimiter, if defined
+# This will allow checking for valid recipient on alias domains
+# even for recipient using delimiter. Eg user+foobar@alias.example.org
+# will correctly check if user@example.org is valid
+my ($ldap, $mesg, @attrs, $result);
+foreach my $url (@ldap_url) {
+ $ldap=Net::LDAPapi->new(-url=>$url);
+ if ( $ldap_starttls_supported ) {
+ $mesg = $ldap->start_tls_s();
+ if ($mesg != 0) {
+ next;
+ }
+ }
+ $mesg = $ldap->bind_s("uid=zimbra,cn=admins,cn=zimbra",$zimbra_pw);
+ if ($mesg != 0) {
+ next;
+ } else {
+ last;
+ }
+}
+if ($mesg == 0){
+ @attrs=('zimbraMtaRecipientDelimiter');
+ $mesg = $ldap->search_s(
+ "",
+ LDAP_SCOPE_SUBTREE,
+ "(&(cn=config)(objectClass=zimbraGlobalConfig))",
+ \@attrs,
+ 0,
+ $result
+ );
+ my $ent = $ldap->first_entry();
+ if ($ent != 0){
+ my $delim = ($ldap->get_values('zimbraMtaRecipientDelimiter'))[0];
+ if ($delim ne ''){
+ $delim_re = qr{[$delim]};
+ syslog $syslog_priority, "Recipient delimiter regex is $delim_re" if $verbose;
+ } else {
+ syslog $syslog_priority, "Recipient delimiter is an empty string so it won't be used" if $verbose;
+ }
+ } else {
+ syslog $syslog_priority, "Recipient delimiter not found" if $verbose;
+ }
+ # Unbind, everything else will bind with the postfix LDAP user
+ $ldap->unbind;
+} else {
+ syslog $syslog_priority, "Couldn't bind with zimbra account, recipient delimiter won't be used" if $verbose;
+}
+
#
# Receive a bunch of attributes, evaluate the policy, send the result.
#

View File

@@ -0,0 +1,11 @@
---
- name: restart zimbra
command: zmcontrol restart
- name: restart rsyslog
service: name=rsyslog state=restarted
- name: restart zmmailboxd
command: /opt/zimbra/bin/zmmailboxdctl restart
become_user: zimbra

View File

@@ -0,0 +1,4 @@
---
dependencies:
- role: mkdir

1
roles/zimbra/service.yml Normal file
View File

@@ -0,0 +1 @@
---

1
roles/zimbra/stats.yml Normal file
View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1 @@
---

239
roles/zimbra/tasks/cas.yml Normal file
View File

@@ -0,0 +1,239 @@
---
- name: Install cas client lib
get_url:
url: "{{ item.url }}"
checksum: sha1:{{ item.sha1 }}
dest: /opt/zimbra/jetty/common/lib/
loop: "{{ zcs_cas_libs }}"
tags: zcs
- name: Get or generate a pre authentication key
shell: |
KEY=$(/opt/zimbra/bin/zmprov getDomain {{ item }} zimbrapreauthkey | perl -ne '/^(?:zimbraP|p)reAuthKey: (.*)/ && print $1')
[ -z $KEY ] && KEY=$(/opt/zimbra/bin/zmprov generateDomainPreAuthKey {{ item }} | perl -ne '/^(?:zimbraP|p)reAuthKey: (.*)/ && print $1')
echo $KEY
become_user: zimbra
register: zcs_preauthkeys
changed_when: False
loop: "{{ zcs_domains.keys() | list }}"
tags: zcs
- name: Install preauth pages
template: src=cas_preauth.jsp.j2 dest=/opt/zimbra/jetty/webapps/zimbra/public/preauth_{{ item.item }}.jsp owner=zimbra group=zimbra
loop: "{{ zcs_preauthkeys.results }}"
notify: restart zimbra
tags: zcs
- name: Install admin preauth pages
template: src=cas_preauth_admin.jsp.j2 dest=/opt/zimbra/jetty/webapps/zimbraAdmin/public/preauth_{{ item.item }}.jsp owner=zimbra group=zimbra
loop: "{{ zcs_preauthkeys.results }}"
notify: restart zimbra
tags: zcs
- name: Configure CAS filters
blockinfile:
path: /opt/zimbra/jetty/etc/zimbra.web.xml.in
block: |2
{% for domain in zcs_domains.keys() | list %}
{% if zcs_domains[domain].cas is defined and zcs_domains[domain].cas.enabled is defined and zcs_domains[domain].cas.enabled %}
<!-- CAS filters for domain {{ domain }} -->
<filter>
<filter-name>CasSingleSignOutFilter{{ domain }}</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>{{ zcs_domains[domain].cas.server_url }}</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CasSingleSignOutFilter{{ domain }}</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter>
<filter-name>CasAuthenticationFilter{{ domain }}</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>{{ zcs_domains[domain].cas.server_url }}/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>{{ zcs_domains[domain].public_url }}</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CasAuthenticationFilter{{ domain }}</filter-name>
<url-pattern>/public/preauth_{{ domain }}.jsp</url-pattern>
</filter-mapping>
<filter>
<filter-name>CasValidationFilter{{ domain }}</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>{{ zcs_domains[domain].cas.server_url }}</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>{{ zcs_domains[domain].public_url }}</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CasValidationFilter{{ domain }}</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- End CAS filter config for domain {{ domain }} -->
{% else %}
<!-- CAS not enabled for domain {{ domain }} -->
{% endif %}
{% endfor %}
<filter>
<filter-name>CasHttpServletRequestWrapperFilter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CasHttpServletRequestWrapperFilter</filter-name>
<url-pattern>/public/*</url-pattern>
</filter-mapping>
<!-- prevent Zimbra from adding ;jsessionid=XXXX in the URL, which the CAS server could reject
as it doesn't match the initial service anymore -->
<session-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
marker: '<!-- "# {mark} ANSIBLE MANAGED BLOCK" -->'
insertafter: '</error-page>'
validate: xmllint %s
notify: restart zimbra
tags: zcs
- name: Configure CAS admin filters
blockinfile:
path: /opt/zimbra/jetty/etc/zimbraAdmin.web.xml.in
block: |2
{% for domain in zcs_domains.keys() | list %}
{% if zcs_domains[domain].cas is defined and zcs_domains[domain].cas.enabled is defined and zcs_domains[domain].cas.enabled %}
<!-- CAS filters for domain {{ domain }} -->
<filter>
<filter-name>CasSingleSignOutFilter{{ domain }}</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>{{ zcs_domains[domain].cas.server_url }}</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CasSingleSignOutFilter{{ domain }}</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter>
<filter-name>CasAuthenticationFilter{{ domain }}</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>{{ zcs_domains[domain].cas.server_url }}/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>{{ zcs_domains[domain].admin_url }}</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CasAuthenticationFilter{{ domain }}</filter-name>
<url-pattern>/public/preauth_{{ domain }}.jsp</url-pattern>
</filter-mapping>
<filter>
<filter-name>CasValidationFilter{{ domain }}</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>{{ zcs_domains[domain].cas.server_url }}</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>{{ zcs_domains[domain].admin_url }}</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CasValidationFilter{{ domain }}</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- End of CAS filters settings for domaine {{ domain }} -->
{% else %}
<!-- CAS not enabled for domain {{ domain }} -->
{% endif %}
{% endfor %}
<filter>
<filter-name>CasHttpServletRequestWrapperFilter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CasHttpServletRequestWrapperFilter</filter-name>
<url-pattern>/public/*</url-pattern>
</filter-mapping>
<!-- prevent Zimbra from adding ;jsessionid=XXXX in the URL, which the CAS server could reject
as it doesn't match the initial service anymore -->
<session-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
marker: '<!-- "# {mark} ANSIBLE MANAGED BLOCK" -->'
insertafter: '</error-page>'
validate: xmllint %s
notify: restart zimbra
tags: zcs
- name: Configure login and logout URL
shell: |
/opt/zimbra/bin/zmprov modifyDomain {{ item }} zimbraWebClientLoginURL "{{ zcs_domains[item].public_url | regex_replace('/$','') }}/public/preauth_{{ item }}.jsp"
/opt/zimbra/bin/zmprov modifyDomain {{ item }} zimbraAdminConsoleLoginURL "{{ zcs_domains[item].admin_url | regex_replace('/$','') }}/zimbraAdmin/public/preauth_{{ item }}.jsp"
/opt/zimbra/bin/zmprov modifyDomain {{ item }} zimbraWebClientLogoutURL "{{ zcs_domains[item].cas.server_url | regex_replace('/$','') }}/logout"
/opt/zimbra/bin/zmprov modifyDomain {{ item }} zimbraAdminConsoleLogoutURL "{{ zcs_domains[item].cas.server_url | regex_replace('/$','') }}/logout"
become_user: zimbra
loop: "{{ zcs_domains.keys() | list }}"
when:
- zcs_domains[item].cas is defined
- zcs_domains[item].cas.enabled is defined
- zcs_domains[item].cas.enabled == True
changed_when: False
tags: zcs

View File

@@ -0,0 +1,5 @@
---
- name: Deploy filebeat configuration
template: src=filebeat.yml.j2 dest=/etc/filebeat/ansible_inputs.d/zimbra.yml
tags: zcs,log

View File

@@ -0,0 +1,36 @@
---
- name: Create temp dir
file: path=/tmp/zimbra state=directory
tags: zcs
- name: Download Zimbra
get_url:
url: "{{ zcs_archive_url }}"
dest: /tmp/zimbra
checksum: "sha1:{{ zcs_archive_sha1 }}"
tags: zcs
- name: Extract Zimbra archive
unarchive:
src: /tmp/zimbra/{{ zcs_archive_name }}
dest: /tmp/zimbra
remote_src: True
tags: zcs
- name: Upload installation scripts
template: src={{ item }}.j2 dest=/tmp/zimbra/{{ item }}
loop:
- zcs_install_answers
- zcs_init_config
tags: zcs
- name: Install Zimbra
shell: ./install.sh -s < /tmp/zimbra/zcs_install_answers
args:
chdir: /tmp/zimbra/{{ zcs_archive_name | splitext | first }}
tags: zcs
- name: Provision initial configuration
shell: /opt/zimbra/libexec/zmsetup.pl -c /tmp/zimbra/zcs_init_config
tags: zcs

View File

@@ -0,0 +1,9 @@
---
- name: Handle Zimbra LDAP ports
iptables_raw:
name: zcs_ldap_ports
state: "{{ (zcs_cluster_ip | length > 0 ) | ternary('present','absent') }}"
rules: "-A INPUT -m state --state NEW -p tcp -m multiport --dports 389,636 -s {{ (zcs_cluster_ip + zcs_ldap_src_ip) | join(',') }} -j ACCEPT"
when: iptables_manage | default(True)
tags: zcs

View File

@@ -0,0 +1,10 @@
---
- name: Handle Zimbra logger ports
iptables_raw:
name: zcs_logger_ports
state: "{{ (zcs_cluster_ip | length > 0 ) | ternary('present','absent') }}"
rules: "-A INPUT -m state --state NEW -p udp --dport 514 -s {{ zcs_cluster_ip | join(',') }} -j ACCEPT"
when: iptables_manage | default(True)
tags: zcs

View File

@@ -0,0 +1,158 @@
---
- name: Install needed tools
yum:
name:
- git
tags: zcs
- name: Configure MySQL bind IP
ini_file:
section: mysqld
option: bind-address
value: 0.0.0.0
path: /opt/zimbra/conf/my.cnf
mode: 0640
owner: zimbra
group: zimbra
notify: restart zimbra
tags: zcs
- name: Get MySQL root password
shell: /opt/zimbra/bin/zmlocalconfig -s mysql_root_password | awk '{ print $3 }'
become_user: zimbra
register: zcs_mysql_root_password
changed_when: False
tags: zcs
- name: Create sqladmin user account
mysql_user:
name: sqladmin
password: '{{ mysql_admin_pass }}'
host: '%'
priv: '*.*:ALL,GRANT'
state: present
login_user: root
login_password: "{{ zcs_mysql_root_password.stdout }}"
login_unix_socket: /opt/zimbra/data/tmp/mysql/mysql.sock
when: mysql_admin_pass is defined
tags: zcs
- name: Handle Zimbra store ports
iptables_raw:
name: zcs_store_ports
state: "{{ (zcs_cluster_ip | length > 0) | ternary('present','absent') }}"
rules: "-A INPUT -m state --state NEW -p tcp -m multiport --dports 143,993,7143,7993 -s {{ zcs_cluster_ip | join(',') }} -j ACCEPT\n
-A INPUT -m state --state NEW -p tcp -m multiport --dports 110,995,7110,7995 -s {{ zcs_cluster_ip | join(',') }} -j ACCEPT\n
-A INPUT -m state --state NEW -p tcp -m multiport --dports 8080,8443,7071,7072,7073 -s {{ zcs_cluster_ip | join(',') }} -j ACCEPT\n
-A INPUT -m state --state NEW -p tcp -m multiport --dports 7025,7026 -s {{ zcs_cluster_ip | join(',') }} -j ACCEPT\n
-A INPUT -m state --state NEW -p tcp -m multiport --dports 8735,8736 -s {{ zcs_cluster_ip | join(',') }} -j ACCEPT"
when: iptables_manage | default(True)
tags: zcs
- name: Handle Zimbra MySQL ports
iptables_raw:
name: zcs_mysql_ports
state: "{{ (zcs_mysql_src_ip | length > 0) | ternary('present','absent') }}"
rules: "-A INPUT -m state --state NEW -p tcp --dport 7306 -s {{ zcs_mysql_src_ip | join(',') }} -j ACCEPT"
when: iptables_manage | default(True)
tags: zcs
#- name: Create ShareToolki cache dir
# file: path=/opt/zimbra/addon_cache/sharetoolkit state=directory
# tags: zcs
#
#- name: Clone ShareToolkit repo
# git:
# repo: https://github.com/Zimbra-Community/shared-mailbox-toolkit.git
# dest: /opt/zimbra/addon_cache/sharetoolkit
# force: True
# register: zcs_sharetoolkit_repo
# notify: restart zmmailboxd
# tags: zcs
#
#- name: Remove previous ShareToolkit client zimlet
# file: path=/opt/zimbra/zimlets-deployed/_dev/tk_barrydegraaff_sharetoolkit_client state=absent
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Create ShareToolkit client Zimlet directory
# file: path=/opt/zimbra/zimlets-deployed/_dev/ state=directory
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Deploy ShareToolkit client zimlet
# copy: src=/opt/zimbra/addon_cache/sharetoolkit/tk_barrydegraaff_sharetoolkit_client dest=/opt/zimbra/zimlets-deployed/_dev/ remote_src=True
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Undeploy previous ShareToolkit Zimlet
# command: /opt/zimbra/bin/zmzimletctl undeploy tk_barrydegraaff_sharetoolkit_admin
# become_user: zimbra
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Create a ZIP archive for ShareToolkit admin zimlet
# archive:
# path: /opt/zimbra/addon_cache/sharetoolkit/tk_barrydegraaff_sharetoolkit_admin/*
# dest: /tmp/tk_barrydegraaff_sharetoolkit_admin.zip
# format: zip
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Deploy new ShareToolkit admin zimlet
# command: /opt/zimbra/bin/zmzimletctl deploy /tmp/tk_barrydegraaff_sharetoolkit_admin.zip
# become_user: zimbra
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Remove previous Java server extension
# file: path=/opt/zimbra/lib/ext/ShareToolkit state=absent
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Create ShareToolkit Java server extension dir
# file: path=/opt/zimbra/lib/ext/ShareToolkit state=directory
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Deploy new Java server extension
# copy: src=/opt/zimbra/addon_cache/sharetoolkit/extension/ShareToolkit/out/artifacts/ShareToolkit/ShareToolkit.jar dest=/opt/zimbra/lib/ext/ShareToolkit/ remote_src=True
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Enable the X-Authenticated-User header
# command: /opt/zimbra/bin/zmprov modifyConfig {{ inner_item.attr }} {{ inner_item.value }}
# become_user: zimbra
# loop:
# - attr: zimbraSmtpSendAddAuthenticatedUser
# value: 'TRUE'
# - attr: zimbraMtaSmtpdSaslAuthenticatedHeader
# value: 'yes'
# loop_control:
# loop_var: inner_item
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Deploy ShareToolkit CLI tools
# synchronize:
# src: /opt/zimbra/addon_cache/sharetoolkit/bin/
# dest: /usr/local/sbin/
# rsync_opts:
# - "--chmod=F755"
# delegate_to: "{{ inventory_hostname }}"
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
#
#- name: Flush Zimbra cache
# command: /opt/zimbra/bin/zmprov flushCache all
# become_user: zimbra
# when: zcs_sharetoolkit_repo.changed
# tags: zcs
- name: Remove ADPassword listener (not working with JDK 13)
file: path=/opt/zimbra/lib/ext/adpassword state=absent
tags: zcs
- include_tasks: cas.yml
tags: zcs

211
roles/zimbra/tasks/main.yml Normal file
View File

@@ -0,0 +1,211 @@
---
#- name: Build config for domains
# set_fact: zcs_domains_conf={{ zcs_domains_conf | default([]) + [zcs_domain_defaults | combine(zcs_domains[item])] }}
# with_items: "{{ zcs_domains.keys() | list }}"
# tags: zcs
#- set_fact: zcs_domains={{ zcs_domains_conf | default([]) }}
# tags: zcs
- include_vars: "{{ item }}"
with_first_found:
- vars/{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml
- vars/{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml
- vars/{{ ansible_distribution }}.yml
- vars/{{ ansible_os_family }}.yml
tags: zcs
- name: Install dependencies
package: name={{ zcs_packages }}
tags: zcs
- name: Check if zimbra is installed
stat: path=/opt/zimbra/bin/zmprov
register: zcs_zmprov
tags: zcs
#- name: Stop postfix
# service: name=postfix state=stopped enabled=False
# tags: zcs
- include_tasks: install.yml
when:
- zcs_install == True
- not zcs_zmprov.stat.exists
tags: zcs
- name: Exit if not installed
meta: end_host
when: zcs_install != True and (zcs_zmprov is not defined or zcs_zmprov.stat is not defined or not zcs_zmprov.stat.exists)
tags: zcs
- set_fact: zcs_i_am_primary_ldap={{ (inventory_hostname == zcs_primary_ldap) | ternary(True,False) }}
tags: zcs
- name: Fetch the LDAP admin pass
shell: /opt/zimbra/bin/zmlocalconfig -s zimbra_ldap_password | awk '{ print $3}'
changed_when: False
register: zcs_ldap_admin_pass
when: zcs_ldap_admin_pass is not defined
tags: zcs
- set_fact: zcs_ldap_admin_pass={{ zcs_ldap_admin_pass.stdout }}
when: zcs_ldap_admin_pass.stdout is defined
tags: zcs
- name: Install wrapper scripts
template: src=zimbra_wrapper.j2 dest=/usr/local/bin/{{ item }} mode=0755
loop:
- zmprov
- zmcontrol
- zmhostname
- zmmailbox
- zmlocalconfig
tags: zcs
#- name: Check installed components
# command: rpm -q zimbra-{{ item }}
# args:
# warn: False
# register: zcs_components
# failed_when: False
# changed_when: False
# loop:
# - ldap
# - logger
# - mta
# - dnscache
# - snmp
# - store
# - apache
# - spell
# - memcached
# - proxy
# - drive
# tags: zcs
- name: List enabled components
shell: '/opt/zimbra/bin/zmprov getServer {{ inventory_hostname }} zimbraServiceEnabled | perl -ne ''m/^zimbraServiceEnabled: (\w+)/ && print "$1\n"'''
become_user: zimbra
register: zcs_enabled_components
changed_when: False
tags: zcs
- set_fact: zcs_enabled_components={{ zcs_enabled_components.stdout_lines }}
tags: zcs
- include_tasks: "{{ component }}.yml"
loop: "{{ zcs_enabled_components }}"
loop_control:
loop_var: component
tags: zcs
- name: Handle general ports
iptables_raw:
name: zcs_general_ports
state: "{{ (zcs_cluster_ip | length > 0) | ternary('present','absent') }}"
rules: "-A INPUT -m state --state NEW -p tcp -m multiport --dports 22 -s {{ zcs_cluster_ip | join(',') }} -j ACCEPT"
when: iptables_manage | default(True)
tags: zcs
- include_tasks: zmldapsync.yml
when: zcs_i_am_primary_ldap == True
tags: zcs
- name: Install backup help script
get_url:
url: https://git.fws.fr/dani/zimbra/raw/branch/master/zmbh/zmbh.pl
dest: /opt/zimbra/bin/zmbh
mode: '0755'
tags: zcs
- name: Remove old backup helper script
file: path=/usr/local/bin/zmbh.pl state=absent
tags: zcs
- name: Create directories
file: path={{ item.dir }} state=directory owner={{ item.owner | default(omit) }} group={{ item.group | default(omit) }} mode={{ item.mode | default(omit) }}
loop:
- dir: /opt/zimbra/addon_cache
- dir: /opt/zimbra/meta
mode: '0700'
tags: zcs
#- include_tasks: backup.yml
# when: zcs_i_am_primary_ldap == True
# tags: zcs
- name: Identify logger host
shell: /opt/zimbra/bin/zmprov gcf zimbraLogHostname | awk '{ print $2 }'
become_user: zimbra
register: zcs_log_hostname
changed_when: False
tags: zcs
- set_fact: zcs_log_hostname={{ zcs_log_hostname.stdout }}
tags: zcs
- name: Deploy syslog config
template: src=rsyslog.conf.j2 dest=/etc/rsyslog.conf
notify: restart rsyslog
tags: zcs
- name: Fix logrotate config to reload rsyslog
replace:
path: /etc/logrotate.d/zimbra
regexp: '^(.*)/var/run/syslog\*\.pid(.*)'
replace: '\1/run/rsyslogd.pid\2'
tags: zcs
- name: Set correct SELinux context
block:
- sefcontext:
target: "/opt/zimbra/log(/.*)?"
setype: var_log_t
- sefcontext:
target: "/etc/rc.d/init.d/zimbra"
setype: bin_t
- command: restorecon -R /opt/zimbra/log /etc/rc.d/init.d/zimbra
changed_when: False
when: ansible_selinux.status == 'enabled'
tags: zcs
- name: Deploy Let's Encrypt hook
template: src=dehydrated_hook.sh.j2 dest=/etc/dehydrated/hooks_deploy_cert.d/20zimbra.sh mode=755
when: zcs_letsencrypt == True
tags: zcs
- name: Remove Let's Encrypt hook
file: path=/etc/dehydrated/hooks_deploy_cert.d/20zimbra.sh state=absent
when: zcs_letsencrypt != True
tags: zcs
- name: Create pre and post backup hook dir
file: path=/etc/backup/{{ item }}.d state=directory
loop:
- pre
- post
tags: zcs
# The cert bundle provided by Zimbra is not very up to date
# so link the system wide one here
- name: Push system trusted CA store to Zimbra
file: src=/etc/pki/tls/cert.pem dest=/opt/zimbra/common/etc/ssl/cert.pem state=link
tags: zcs
- name: Deploy pre and post backup scripts
template: src={{ item }}_backup.sh.j2 dest=/etc/backup/{{ item }}.d/zimbra.sh mode=0750
loop:
- pre
- post
tags: zcs
- name: Create backup mount point
file: path=/home/lbkp/zimbra state=directory
tags: zcs
- name: Remove temp files
file: path={{ item }} state=absent
loop:
- /tmp/zimbra
- /tmp/tk_barrydegraaff_sharetoolkit_admin.zip
tags: zcs
- include: filebeat.yml

View File

@@ -0,0 +1,10 @@
---
- name: Handle Zimbra memcached ports
iptables_raw:
name: zcs_memcached_ports
state: "{{ (zcs_cluster_ip | length > 0 ) | ternary('present','absent') }}"
rules: "-A INPUT -m state --state NEW -p tcp -m multiport --dports 11211 -s {{ zcs_cluster_ip | join(',') }} -j ACCEPT\n
-A INPUT -m state --state NEW -p udp -m multiport --dports 11211 -s {{ zcs_cluster_ip | join(',') }} -j ACCEPT"
when: iptables_manage | default(True)
tags: zcs

View File

@@ -0,0 +1,36 @@
---
- name: Configure Zimbra's sendmail as alternative to sendmail
alternatives:
name: mta
link: /usr/sbin/sendmail
path: /opt/zimbra/common/sbin/sendmail
priority: 90
tags: zcs
- name: Set zimbra's sendmail as the default sendmail
command: update-alternatives --set mta /opt/zimbra/common/sbin/sendmail
changed_when: False
tags: zcs
- name: Override zmpostfixpolicyd to support recipient delimiter
copy: src=zmpostfixpolicyd dest=/opt/zimbra/libexec/zmpostfixpolicyd
notify: restart zimbra
tags: zcs
- name: Handle Zimbra mta ports
iptables_raw:
name: zcs_mta_ports
state: present
rules: "-A INPUT -m state --state NEW -p tcp -m multiport --dports 25,465,587 -j ACCEPT"
when: iptables_manage | default(True)
tags: zcs
- name: Enable always_bcc
lineinfile:
dest: /opt/zimbra/common/conf/main.cf
regexp: '^always_bcc =.*'
line: always_bcc = {{ zcs_always_bcc }}
when: zcs_always_bcc is defined
notify: restart zimbra
tags: zcs

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,32 @@
---
- name: Handle Zimbra proxy ports
iptables_raw:
name: zcs_proxy_ports
state: present
rules: "{% if zcs_http_src_ip | length > 0 %}-A INPUT -m state --state NEW -p tcp -m multiport --dports 80,443 -s {{ zcs_http_src_ip | join(',') }} -j ACCEPT\n{% endif %}
{% if zcs_clients_src_ip | length > 0 %}-A INPUT -m state --state NEW -p tcp -m multiport --dports 110,995,143,993 -s {{ zcs_clients_src_ip | join(',') }} -j ACCEPT\n{% endif %}
{% if zcs_admin_src_ip | length > 0 %}-A INPUT -m state --state NEW -p tcp -m multiport --dports 9071 -s {{ zcs_admin_src_ip | join(',') }} -j ACCEPT{% endif %}"
when: iptables_manage | default(True)
tags: zcs
- name: Enable proxy for the admin interface
command: /opt/zimbra/bin/zmprov ms {{ inventory_hostname }} zimbraReverseProxyAdminEnabled TRUE
changed_when: False
become_user: zimbra
tags: zcs
- name: Build a list of vhosts to be used for Let's Encrypt cert
shell: |
for DOMAIN in $(/opt/zimbra/bin/zmprov getAllDomains); do
/opt/zimbra/bin/zmprov getDomain $DOMAIN zimbraVirtualHostname | perl -ne 'm/^zimbraVirtualHostname: (.*)/ && print "$1\n"'
done
become_user: zimbra
register: zcs_vhosts
changed_when: False
when: zcs_letsencrypt == True
tags: zcs
- set_fact: zcs_vhosts={{ zcs_vhosts.stdout_lines }}
when: zcs_vhosts.stdout_lines is defined
tags: zcs

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,9 @@
---
- name: Handle Zimbra spell ports
iptables_raw:
name: zcs_spell_ports
state: "{{ (zcs_cluster_ip | length > 0) | ternary('present','absent') }}"
rules: "-A INPUT -m state --state NEW -p tcp --dport 7780 -s {{ zcs_http_src_ip | join(',') }} -j ACCEPT"
when: iptables_manage | default(True)
tags: zcs

View File

@@ -0,0 +1,56 @@
---
- name: Install zmldapsync dependencies
yum:
name:
- perl-LDAP
- perl-YAML-Tiny
- perl-Data-UUID
- perl-String-ShellQuote
- perl-Array-Diff
- perl-List-MoreUtils
- perl-Hash-Merge-Simple
- perl-Text-Unidecode
- perl-Email-MIME
- perl-Email-Sender
tags: zcs
- name: Install zmldapsync
get_url:
url: "{{ item.url }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
loop:
- url: https://git.fws.fr/dani/zimbra/raw/branch/master/zmldapsync/zmldapsync.pl
dest: /opt/zimbra/bin/zmldapsync
mode: 755
- url: https://git.fws.fr/dani/zimbra/raw/branch/master/zmldapsync/zmldapsync.yml
dest: /opt/zimbra/conf/zmldapsync.yml.dist
mode: 640
tags: zcs
- name: Remove old zmldapsync script
file: path=/opt/zimbra/bin/zmldapsync.pl state=absent
tags: zcs
- name: Deploy zmldapsync configuration
template: src=zmldapsync.yml.j2 dest=/opt/zimbra/conf/zmldapsync.yml owner=root group=zimbra mode=0640
tags: zcs
- name: Deploy zmldapsync systemd units
template: src={{ item }}.j2 dest=/etc/systemd/system/{{ item }}
loop:
- zmldapsync.service
- zmldapsync.timer
register: zcs_zmldapsync_unit
tags: zcs
- name: Reload systemd
systemd: daemon_reload=True
when: zcs_zmldapsync_unit.changed
tags: zcs
- name: Enable zmldapsync timer
systemd: name=zmldapsync.timer state={{ (zcs_domains.keys() | length > 0) | ternary('started','stopped') }} enabled={{ (zcs_domains.keys() | length > 0) | ternary(True,False) }}
when: zcs_domains.keys() | length > 0
tags: zcs

View File

@@ -0,0 +1,98 @@
<%@ page import="java.security.InvalidKeyException" %>
<%@ page import="java.security.NoSuchAlgorithmException" %>
<%@ page import="java.security.SecureRandom" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="java.util.TreeSet" %>
<%@ page import="javax.crypto.Mac" %>
<%@ page import="javax.crypto.SecretKey" %>
<%!
public static final String DOMAIN_KEY =
"{{ item.stdout }}";
public static String generateRedirect(HttpServletRequest request, String name) {
HashMap params = new HashMap();
String ts = System.currentTimeMillis()+"";
params.put("account", name);
params.put("by", "name"); // needs to be part of hmac
params.put("timestamp", ts);
params.put("expires", "0"); // means use the default
String preAuth = computePreAuth(params, DOMAIN_KEY);
return request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+"/service/preauth/?" +
"account="+name+
"&by=name"+
"&timestamp="+ts+
"&expires=0"+
"&preauth="+preAuth;
}
public static String computePreAuth(Map params, String key) {
TreeSet names = new TreeSet(params.keySet());
StringBuffer sb = new StringBuffer();
for (Iterator it=names.iterator(); it.hasNext();) {
if (sb.length() > 0) sb.append('|');
sb.append(params.get(it.next()));
}
return getHmac(sb.toString(), key.getBytes());
}
private static String getHmac(String data, byte[] key) {
try {
ByteKey bk = new ByteKey(key);
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(bk);
return toHex(mac.doFinal(data.getBytes()));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("fatal error", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("fatal error", e);
}
}
static class ByteKey implements SecretKey {
private byte[] mKey;
ByteKey(byte[] key) {
mKey = (byte[]) key.clone();;
}
public byte[] getEncoded() {
return mKey;
}
public String getAlgorithm() {
return "HmacSHA1";
}
public String getFormat() {
return "RAW";
}
}
public static String toHex(byte[] data) {
StringBuilder sb = new StringBuilder(data.length * 2);
for (int i=0; i<data.length; i++ ) {
sb.append(hex[(data[i] & 0xf0) >>> 4]);
sb.append(hex[data[i] & 0x0f] );
}
return sb.toString();
}
private static final char[] hex =
{'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' ,
'8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f'};
%><%
String casUser = request.getRemoteUser().toString().trim();
String redirect = generateRedirect(request, casUser+"@{{ item.item }}");
response.sendRedirect(redirect);
%>
<html>
<head>
<title>Pre-auth redirect</title>
</head>
<body>You should never see this page!</body>
</html>

View File

@@ -0,0 +1,100 @@
<%@ page import="java.security.InvalidKeyException" %>
<%@ page import="java.security.NoSuchAlgorithmException" %>
<%@ page import="java.security.SecureRandom" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="java.util.TreeSet" %>
<%@ page import="javax.crypto.Mac" %>
<%@ page import="javax.crypto.SecretKey" %>
<%!
public static final String DOMAIN_KEY =
"{{ item.stdout }}";
public static String generateRedirect(HttpServletRequest request, String name) {
HashMap params = new HashMap();
String ts = System.currentTimeMillis()+"";
params.put("account", name);
params.put("by", "name"); // needs to be part of hmac
params.put("timestamp", ts);
params.put("expires", "0"); // means use the default
params.put("admin", "1");
String preAuth = computePreAuth(params, DOMAIN_KEY);
return request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+"/service/preauth/?" +
"account="+name+
"&by=name"+
"&timestamp="+ts+
"&expires=0"+
"&admin=1"+
"&preauth="+preAuth;
}
public static String computePreAuth(Map params, String key) {
TreeSet names = new TreeSet(params.keySet());
StringBuffer sb = new StringBuffer();
for (Iterator it=names.iterator(); it.hasNext();) {
if (sb.length() > 0) sb.append('|');
sb.append(params.get(it.next()));
}
return getHmac(sb.toString(), key.getBytes());
}
private static String getHmac(String data, byte[] key) {
try {
ByteKey bk = new ByteKey(key);
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(bk);
return toHex(mac.doFinal(data.getBytes()));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("fatal error", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("fatal error", e);
}
}
static class ByteKey implements SecretKey {
private byte[] mKey;
ByteKey(byte[] key) {
mKey = (byte[]) key.clone();;
}
public byte[] getEncoded() {
return mKey;
}
public String getAlgorithm() {
return "HmacSHA1";
}
public String getFormat() {
return "RAW";
}
}
public static String toHex(byte[] data) {
StringBuilder sb = new StringBuilder(data.length * 2);
for (int i=0; i<data.length; i++ ) {
sb.append(hex[(data[i] & 0xf0) >>> 4]);
sb.append(hex[data[i] & 0x0f] );
}
return sb.toString();
}
private static final char[] hex =
{'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' ,
'8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f'};
%><%
String casUser = request.getRemoteUser().toString().trim();
String redirect = generateRedirect(request, casUser+"@{{ item.item }}");
response.sendRedirect(redirect);
%>
<html>
<head>
<title>Pre-auth redirect</title>
</head>
<body>You should never see this page!</body>
</html>

View File

@@ -0,0 +1,60 @@
#!/bin/bash -e
{% if zcs_letsencrypt == True %}
if [ $1 == "{{ inventory_hostname }}" ]; then
cat /var/lib/dehydrated/certificates/certs/{{ inventory_hostname }}/privkey.pem > /opt/zimbra/ssl/zimbra/commercial/commercial.key
chown zimbra:zimbra /opt/zimbra/ssl/zimbra/commercial/commercial.key
chmod 600 /opt/zimbra/ssl/zimbra/commercial/commercial.key
cp /var/lib/dehydrated/certificates/certs/{{ inventory_hostname }}/cert.pem /tmp/zimbra_cert.pem
cp /var/lib/dehydrated/certificates/certs/{{ inventory_hostname }}/chain.pem /tmp/zimbra_chain.pem
# Zimbra needs the root cert to validate the whole chain
cat <<_EOF >> /tmp/zimbra_chain.pem
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
_EOF
chown zimbra:zimbra /opt/zimbra/ssl/zimbra/commercial/commercial.key /tmp/zimbra_{cert,chain}.pem
su - zimbra -c "/opt/zimbra/bin/zmcertmgr deploycrt comm /tmp/zimbra_cert.pem /tmp/zimbra_chain.pem"
rm -f /tmp/zimbra_{cert,chain}.pem
{% if 'mta' in zcs_enabled_components %}
su - zimbra -c "/opt/zimbra/bin/zmmtactl reload"
{% endif %}
{% if 'proxy' in zcs_enabled_components %}
# Don't put the root cert for nginx, as some monitoring tools might not like this
cat /var/lib/dehydrated/certificates/certs/{{ inventory_hostname }}/fullchain.pem > /opt/zimbra/conf/nginx.crt
su - zimbra -c "/opt/zimbra/bin/zmproxyctl reload"
{% endif %}
{% if 'mailbox' in zcs_enabled_components %}
su - zimbra -c "/opt/zimbra/bin/zmmailboxdctl reload"
{% endif %}
fi
{% endif %}

View File

@@ -0,0 +1,9 @@
- type: log
enabled: True
paths:
- /var/log/zimbra.log
- /opt/zimbra/log/*.log
exclude_files:
- '\.[gx]z$'
- '\d+$'

View File

@@ -0,0 +1,11 @@
#!/bin/sh
set -eo pipefail
echo 'Removing Zimbra database dumps'
rm -f /home/lbkp/zimbra/{ldap*,mysql*}
rm -rf /home/lbkp/zimbra/exports/
# Remove snapshot mount point
echo 'Cleanup backup mount point and snapshot'
/opt/zimbra/bin/zmbh --post --shutdown=ldap --verbose --mount=/home/lbkp/zimbra/mount

View File

@@ -0,0 +1,68 @@
#!/bin/sh
set -eo pipefail
mkdir -p /home/lbkp/zimbra/mount
source /opt/zimbra/bin/zmshutil
zmsetvars
echo 'Starting Zimbra backup'
{% if 'ldap' in zcs_enabled_components %}
echo 'Dumping LDAP database'
# Dump ldap data
/opt/zimbra/common/sbin/slapcat \
-F /opt/zimbra/data/ldap/config \
-n 0 | zstd -c > /home/lbkp/zimbra/ldap-config.ldif.zst
/opt/zimbra/common/sbin/slapcat \
-F /opt/zimbra/data/ldap/config \
-b "" | zstd -c > /home/lbkp/zimbra/ldap.ldif.zst
{% endif %}
{% if 'mailbox' in zcs_enabled_components %}
# Dump MySQL data
echo 'Dumping MySQL database'
/opt/zimbra/common/bin/mysqldump \
--user=root \
--password=$mysql_root_password \
--socket=$mysql_socket \
--all-databases \
--single-transaction \
--flush-logs | zstd -c > /home/lbkp/zimbra/mysql.sql.zst
# Export calendars, tasks and address books to ics and vcf files
OLDIFS=$IFS
IFS=$'\n'
for MAILBOX in $(/usr/local/bin/zmprov getQuotaUsage $(hostname --fqdn) | awk '{ print $1}'); do
echo ''
echo "Cheking contact and calendar folders for $MAILBOX"
for LINE in $(/usr/local/bin/zmmailbox -z -m $MAILBOX -t 0 getAllFolders); do
# Skip folders whose ID indicates it's a shared folder
if echo $LINE | grep -qiP '^\s*[a-z0-9]{8}\-[a-z0-9]'; then
continue
fi
DIR=$(echo $LINE | perl -ne 'm/\s*\d+\s+(\w{4})\s+\d+\s+\d+\s+(\/.*)/ && print "$2\n"')
if echo $DIR | grep -qiP '\([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}:\d+\)$'; then
continue
fi
TYPE=$(echo $LINE | perl -ne 'm/\s*\d+\s+(\w{4})\s+\d+\s+\d+\s+(\/.*)/ && print "$1\n"')
if [ "$TYPE" == "appo" -o "$TYPE" == "task" -o "$TYPE" == "cont" ]; then
echo "Exporting folder $DIR (account $MAILBOX, type $TYPE)"
mkdir -p /home/lbkp/zimbra/exports/$MAILBOX/$(dirname $DIR)
fi
if [ "$TYPE" == "appo" -o "$TYPE" == "task" ]; then
/usr/local/bin/zmmailbox -z -m $MAILBOX -t 0 getRestUrl "$(printf '%q' $DIR)?fmt=ics" > "/home/lbkp/zimbra/exports/$MAILBOX/$DIR"".ics"
elif [ "$TYPE" == "cont" ]; then
/usr/local/bin/zmmailbox -z -m $MAILBOX -t 0 getRestUrl "$(printf '%q' $DIR)?fmt=vcf" > "/home/lbkp/zimbra/exports/$MAILBOX/$DIR"".vcf"
fi
done
done
IFS=$OLDIFS
{% endif %}
# Try to snapshot Zimbra tree
echo ''
echo "Handle /opt/zimbra snapshot / bind mount on /home/lbkp/zimbra/mount"
/opt/zimbra/bin/zmbh --pre --snap-size=10G --verbose --mount=/home/lbkp/zimbra/mount --shutdown=ldap

View File

@@ -0,0 +1,23 @@
$ModLoad imuxsock
$ModLoad imjournal
$OmitLocalLogging on
$IMJournalStateFile imjournal.state
$WorkDirectory /var/lib/rsyslog
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
{% if 'logger' in zcs_enabled_components %}
$ModLoad imudp
$UDPServerRun 514
local0.* -/var/log/zimbra.log
local1.* -/var/log/zimbra-stats.log
auth.* -/var/log/zimbra.log
mail.* -/var/log/zimbra.log
{% else %}
local0.* @{{ zcs_log_hostname }}
local1.* @{{ zcs_log_hostname }}
auth.* @{{ zcs_log_hostname }}
local0.* -/var/log/zimbra.log
local1.* -/var/log/zimbra-stats.log
auth.* -/var/log/zimbra.log
mail.* @{{ zcs_log_hostname }}
mail.* -/var/log/zimbra.log
{% endif %}

View File

@@ -0,0 +1,86 @@
AVDOMAIN="{{ zcs_main_domain }}"
AVUSER="admin@{{ zcs_main_domain }}"
CREATEADMIN="admin@{{ zcs_main_domain }}"
CREATEADMINPASS="zimbraadmin"
CREATEDOMAIN="{{ zcs_main_domain }}"
DOCREATEADMIN="yes"
DOCREATEDOMAIN="yes"
DOTRAINSA="yes"
EXPANDMENU="no"
HOSTNAME="{{ inventory_hostname }}"
HTTPPORT="8080"
HTTPPROXYPORT="80"
HTTPSPORT="8443"
HTTPSPROXYPORT="443"
IMAPPORT="7143"
IMAPPROXYPORT="143"
IMAPSSLPORT="7993"
IMAPSSLPROXYPORT="993"
INSTALL_WEBAPPS="service zimlet zimbra zimbraAdmin"
JAVAHOME="/opt/zimbra/common/lib/jvm/java"
LDAPBESSEARCHSET="set"
LDAPHOST="{{ zcs_primary_ldap }}"
LDAPPORT="389"
LDAPREPLICATIONTYPE="{{ zcs_i_am_primary_ldap | ternary('master','salve') }}"
LDAPSERVERID="2"
MAILBOXDMEMORY="{{ (ansible_memtotal_mb * 0.25) | int }}"
MAILPROXY="TRUE"
MODE="redirect"
MYSQLMEMORYPERCENT="30"
POPPORT="7110"
POPPROXYPORT="110"
POPSSLPORT="7995"
POPSSLPROXYPORT="995"
PROXYMODE="redirect"
REMOVE="no"
RUNARCHIVING="no"
RUNAV="{{ (zcs_run_av == True) | ternary('yes','no') }}"
RUNCBPOLICYD="{{ (zcs_run_cbpolicyd == True) | ternary('yes','no') }}"
RUNDKIM="{{ (zcs_run_dkim == True) | ternary('yes','no') }}"
RUNSA="{{ (zcs_run_sa == True) | ternary('yes','no') }}"
RUNVMHA="no"
SERVICEWEBAPP="yes"
SMTPDEST="admin@{{ zcs_main_domain }}"
SMTPHOST="{{ inventory_hostname }}"
SMTPNOTIFY="yes"
SMTPSOURCE="admin@{{ zcs_main_domain }}"
SNMPNOTIFY="{{ ('snmp' in zcs_components) | ternary('yes','no') }}"
SNMPTRAPHOST="{{ inventory_hostname }}"
SPELLURL="http://{{ inventory_hostname }}:7780/aspell.php"
STARTSERVERS="yes"
STRICTSERVERNAMEENABLED="TRUE"
TRAINSAHAM="ham.{{ ('ham' | password_hash('sha256', 65534 | random(seed=zcs_main_domain) | string))[9:17] }}@{{ zcs_main_domain }}"
TRAINSASPAM="spam.{{ ('spam' | password_hash('sha256', 65534 | random(seed=zcs_main_domain) | string))[9:17] }}@{{ zcs_main_domain }}"
UIWEBAPPS="yes"
UPGRADE="yes"
USEEPHEMERALSTORE="no"
USESPELL="{{ ('spell' in zcs_components) | ternary('yes','no') }}"
VERSIONUPDATECHECKS="TRUE"
VIRUSQUARANTINE="virus-quarantine.{{ ('virus' | password_hash('sha256', 65534 | random(seed=zcs_main_domain) | string))[9:17] }}@{{ zcs_main_domain }}"
ZIMBRA_REQ_SECURITY="yes"
ldap_bes_searcher_password="{{ ('bes_searcher' | password_hash('sha256', 65534 | random(seed=zcs_main_domain) | string))[9:22] }}"
ldap_dit_base_dn_config="cn=zimbra"
ldap_nginx_password="{{ ('nginx' | password_hash('sha256', 65534 | random(seed=zcs_main_domain) | string))[9:22] }}"
mailboxd_directory="/opt/zimbra/mailboxd"
mailboxd_keystore="/opt/zimbra/mailboxd/etc/keystore"
mailboxd_keystore_password="{{ ('keystore' | password_hash('sha256', 65534 | random(seed=zcs_main_domain) | string))[9:22] }}"
mailboxd_server="jetty"
mailboxd_truststore="/opt/zimbra/common/lib/jvm/java/lib/security/cacerts"
mailboxd_truststore_password="changeit"
postfix_mail_owner="postfix"
postfix_setgid_group="postdrop"
ssl_default_digest="sha256"
zimbraFeatureBriefcasesEnabled="Enabled"
zimbraFeatureTasksEnabled="Enabled"
zimbraIPMode="ipv4"
zimbraMailProxy="{{ ('proxy' in zcs_components) | ternary('TRUE','FALSE') }}"
zimbraMtaMyNetworks="127.0.0.0/8"
zimbraPrefTimeZoneId="{{ system_tz | default('Europe/Paris') }}"
zimbraReverseProxyLookupTarget="TRUE"
zimbraVersionCheckNotificationEmail="admin@{{ zcs_main_domain }}"
zimbraVersionCheckNotificationEmailFrom="admin@{{ zcs_main_domain }}"
zimbraVersionCheckSendNotifications="TRUE"
zimbraWebProxy="{{ ('proxy' in zcs_components) | ternary('TRUE','FALSE') }}"
zimbra_ldap_userdn="uid=zimbra,cn=admins,cn=zimbra"
zimbra_require_interprocess_security="1"
INSTALL_PACKAGES="zimbra-core {{ zcs_components | map('regex_replace', '^(.*)$', 'zimbra-\\1') | join(' ') }} "

View File

@@ -0,0 +1,6 @@
{# Accept License #}y
{# Use Online repo #}y
{% for component in [ 'ldap', 'logger', 'mta', 'dnscache', 'snmp', 'store', 'apache', 'spell', 'memcached', 'proxy', 'drive', 'imapd', 'chat' ] %}
{{ (component in zcs_components) | ternary('y','n') }}
{% endfor %}
{# Confirm continue #}y

View File

@@ -0,0 +1,3 @@
#!/bin/bash -e
su - zimbra -c "LC_ALL=C.utf8 /opt/zimbra/bin/{{ item }} $*"

View File

@@ -0,0 +1,7 @@
[Unit]
Description=Sync LDAP accounts into Zimbra
[Service]
Type=oneshot
ExecStart=/opt/zimbra/bin/zmldapsync
TimeoutSec=300

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Sync LDAP Users with Zimbra
[Timer]
OnCalendar=*:0/5
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
---
general:
notify:
from: zimbra@{{ ansible_domain }}
to: {{ system_admin_email }}
domains:
{% for domain in zcs_domains.keys() %}
{% if zcs_domains[domain].ldapsync is defined %}
{{ domain }}:
{{ zcs_domains[domain].ldapsync | to_nice_yaml(indent=2) | indent(width=4, first=True) }}
{% endif %}
{% endfor %}

View File

@@ -0,0 +1,7 @@
---
zcs_packages:
- tar
- MySQL-python
- perl-JSON
- patch

View File

@@ -0,0 +1,7 @@
---
zcs_packages:
- tar
- python3-mysql
- perl-JSON
- patch

View File

@@ -0,0 +1,2 @@
---
system_postfix: False

1
roles/zimbra/zimbra.yml Normal file
View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1 @@
---

1
roles/zimbra/zimlet.yml Normal file
View File

@@ -0,0 +1 @@
---