Skip to content

Commit

Permalink
Merge pull request benningm#30 from falon/master
Browse files Browse the repository at this point in the history
A complete example of accounting over LDAP
  • Loading branch information
benningm authored Jun 22, 2018
2 parents 25d4d7a + 2012886 commit 7599f20
Show file tree
Hide file tree
Showing 3 changed files with 351 additions and 0 deletions.
42 changes: 42 additions & 0 deletions LDAP/97mtpolicyd.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
dn: cn=schema
#Attributes
#
attributeTypes: ( mtpolicydMailMessageLimit-oid
NAME ( 'mtpolicydMailMessageLimit' )
DESC 'MtPolicyd user defined attribute for enable accounting over count messages'
EQUALITY integerOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE
X-ORIGIN 'MtPolicyd' )
attributeTypes: ( mtpolicydMailRecipientLimit-oid
NAME ( 'mtpolicydMailRecipientLimit' )
DESC 'MtPolicyd user defined attribute for enable accounting over recipient count messages'
EQUALITY integerOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE
X-ORIGIN 'MtPolicyd' )
attributeTypes: ( mtpolicydMailSizeLimit-oid
NAME ( 'mtpolicydMailSizeLimit' )
DESC 'MtPolicyd user defined attribute for enable accounting over size limit'
EQUALITY integerOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE
X-ORIGIN 'MtPolicyd' )
attributeTypes: ( mtpolicydMailSizeRecipientLimit-oid
NAME ( 'mtpolicydMailSizeRecipientLimit' )
DESC 'MtPolicyd user defined attribute for enable accounting over size x recipient limit'
EQUALITY integerOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE
X-ORIGIN 'MtPolicyd' )
#
#
#Objectclasses
objectclasses: ( mtpolicyd-oid
NAME 'mtpolicyd'
DESC 'mtPolicyd class for user level configuration'
SUP mailRecipient
AUXILIARY
MUST ( )
MAY ( mtpolicydMailMessageLimit $ mtpolicydMailRecipientLimit $ mtpolicydMailSizeLimit $ mtpolicydMailSizeRecipientLimit )
X-ORIGIN 'MtPolicyd' )
193 changes: 193 additions & 0 deletions LDAP/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# A case study over LDAP, Postfix and MtPolicyd
## Abstract
With MtPolicyd you can use LDAP to profile your accounts with policies. For instance, for each **account** you can set a number of maximum message rate (for single message, or message x recipient), or a size rate too.
We see how to implement these policies per account with a working example. Suitable for a large environment.

## Requisite
Many email service implementations adopt LDAP as a DB to profile user preferences, SMTP routing information and authentication. You should have an LDAP server (ldap.example.com) with email account like this:

```
dn: [email protected],[base dn]
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: mailRecipient
objectClass: inetMailUser
mailAlternateAddress: [email protected]
mail: [email protected]
mailDeliveryOption: mailbox
uid: [email protected]
userPassword: mypassword
cn: Account name
mailUserStatus: active
sn: Account
mailHost: imapserver.example.com
```

In this example the uid is the `sasl_username` used by Postfix to authenticate and authorize the account to send mail.
You can build this authentication process using saslauthd over LDAP mechanism, for instance.

Here we don't explain how to implement authentication, other SMTP routing mechanism or email aliases over LDAP. Anyway, let suppose the above entry is a working LDAP account used for SMTP authentication.

You can imagine other policies for _client_address_ or other keys too, using different Postfix _context_. This is not the scope of this document.

Postfix, Mtpolicyd and the LDAP server could stay on different hosts. All of them can interface each other through TCP sockets. For instance you can install

* MtPolicyd on mtpolicyd.example.com
* Postfix on postfix.example.com
* Directory Server on ldap.example.com

## Configure
As you can see in [Plugin Accounting](http://search.cpan.org/~benning/Mail-MtPolicyd-1.16/lib/Mail/MtPolicyd/Plugin/Accounting.pm), we have four counters for each key. Our key will be `sasl_username`, because we want policies per account. So we first have to declare a schema for the LDAP server.

Mtpolicyd doesn't provide an official schema. Here you can find a schema useful for the result we want achieve in this case. The schema works with Red Hat/Fedora Directory Server, but with little adjustment probably can work with OpenLDAP or other Directory Servers which support custom, **unofficial OIDs**.

This unofficial schema provides the attributes for the four counters:
* mtpolicydMailMessageLimit
* mtpolicydMailRecipientLimit
* mtpolicydMailSizeLimit
* mtpolicydMailSizeRecipientLimit

These attributes comes with the objectClass
* mtpolicyd

which extend the objectclass "mailRecipient". This choice is not mandatory, you can change it if you don't like it.

Once you have extended the schema, our LDAP entry can be profiled for MtPolicyd.
For instance we can choose to limit the account "[email protected]" to send a maximum of 100 mails per time unit.
To achieve this the entry is:

```
dn: [email protected],[base dn]
mtpolicydMailMessageLimit: 100
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: mailRecipient
objectClass: inetMailUser
objectClass: mtpolicyd
mailAlternateAddress: [email protected]
mail: [email protected]
mailDeliveryOption: mailbox
uid: [email protected]
userPassword: mypassword
cn: Account name
mailUserStatus: active
sn: Account
mailHost: imapserver.example.com
```

We could also set the `mtpolicydMailRecipientLimit` attribute and configure MtPolicyd to refuse the mails if at least one counter triggers the threshold defined in the LDAP attribute. So, here is the complete MtPolicyd virtual host:

```
vhost_by_policy_context=1
<VirtualHost 12345>
name="accounting"
<Plugin LdapUID>
module="LdapUserConfig"
basedn="[base dn]"
# sasl_username attribute is uid.
filter_field="sasl_username"
filter="(&(uid=%s)(objectClass=mailRecipient)(objectclass=mtpolicyd)(mailUserStatus=active))"
# copy these fields to current mtpolicyd session
config_fields="mtpolicydMailMessageLimit,mtpolicydMailRecipientLimit"
</Plugin>
<Plugin QuotaUser>
module = "Quota"
time_pattern = "%Y-%m-%d"
field = "sasl_username"
metric = "count"
threshold = 500
# if this field is set it will overwrite the default threshold
uc_threshold = "mtpolicydMailMessageLimit"
# for MSA you may reject, for MTAs you may defer
action = "reject you exceeded your daily message limit"
</Plugin>
<Plugin QuotaUserRecipient>
module = "Quota"
time_pattern = "%Y-%m-%d"
field = "sasl_username"
metric = "count_rcpt"
threshold = 5000
# if this field is set it will overwrite the default threshold
uc_threshold = "mtpolicydMailRecipientLimit"
# for MSA you may reject, for MTAs you may defer
action = "reject you exceeded your daily mail recipient limit"
</Plugin>
<Plugin AcctUser>
module = "Accounting"
fields = "sasl_username"
# Perform day based limit
time_pattern = "%Y-%m-%d"
</Plugin>
</VirtualHost>
```
To understand how it works, we strongly suggest to read the [How to Accounting Quota CookBook](https://metacpan.org/pod/release/BENNING/Mail-MtPolicyd-2.03/lib/Mail/MtPolicyd/Cookbook/HowtoAccountingQuota.pod).

In this example the rate time unit is _day_, but you can configure hours or other just setting the proper `time_pattern`.

### Postfix interface
This is very simple. The main.cf of the Postfix server can be configured with
```
smtpd_end_of_data_restrictions =
check_policy_service {
inet:mtpolicyd.example.com:12345,
policy_context=accounting
}
```

### LDAP connection
This is an example for the LDAP connection:

```
<Connection ldap>
module = "Ldap"
host = "ldap.example.com"
port = 389
timeout = 20
binddn = "uid=mtpolicyd,ou=admins,[base dn]"
password = "mtpolicyd"
starttls = 0
</Connection>
```
Don't worry if connections between MtPolicyd and LDAP server die. MtPolicyd checks if the connection is alive. If the connection dies, MtPolicyd tries to renegotiate it. This behavior has tested with load balancer and LDAP server which expires idle sessions.

The user _mtpolicyd_ can be:

```
dn: uid=mtpolicyd,ou=admins,[base dn]
uid: mtpolicyd
givenName: Mail Team
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetorgperson
sn: Policyd
cn: Mail Team Policyd
userPassword: mtpolicyd
```

Remember to set a Password Policy which doesn't expire the password of the user mtpolicyd.

On [base dn], or where mail accounts stay, you can set an aci:

```
aci: (targetattr = "objectClass || mtpolicydMailSizeLimit || uid || mtpolicydM
ailSizeRecipientLimit || mtpolicydMailMessageLimit || mailUserStatus || mtpol
icydMailRecipientLimit") (target = "ldap:///[base dn]")
(targetfilter = objectclass=mtpolicyd) (version 3.0;acl "Allow MtPolicyd access
";allow (read,compare,search)(userdn = "ldap:///uid=mtpolicyd,ou=admins,[base dn]");)
```

This aci limits what user mtpolicyd can perform over LDAP data. But you can imagine more complex situations, where an aci time-defined can enforce a policy only during a specific time interval, such as night hours or weekend.

## The complete example
* [LDAP schema](97mtpolicyd.ldif)
* [a complete mtpolicyd.conf example](mtpolicyd.conf)
116 changes: 116 additions & 0 deletions LDAP/mtpolicyd.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Configuration for the mailteam policy daemon

user=mtpolicyd
group=mtpolicyd

# 0=>'err', 1=>'warning', 2=>'notice', 3=>'info', 4=>'debug' (default: 2)
log_level=2

host=10.10.10.10
port="10.10.10.10:12345"

min_servers=4
min_spare_servers=4
max_spare_servers=12
max_servers=50
max_requests=1000

#keepalive_timeout=60
keepalive_timeout=0
# should be the same value as smtpd_policy_service_reuse_count_limit (postfix >2.12)
max_keepalive=0
#max_keepalive=100

# timeout for processing of one request in seconds
request_timeout=20

vhost_by_policy_context=1

<Connection memcached>
module = "Memcached"
servers = "127.0.0.1:11211"
# namespace = "mt-"
</Connection>

# mysql for storing accounting tables
<Connection db>
module = "Sql"
# see perldoc DBI for syntax of dsn connection string
dsn = "dbi:mysql:database=mailpolicy;host=localhost;port=3306"
user = "mtpolicyd"
password = "mysqlpassword"
</Connection>

# ldap with user configuration
<Connection ldap>
module = "Ldap"
host = "ldap.example.com"
port = 389
timeout = 20
binddn = "uid=mtpolicyd,o=admins,c=en"
password = "ldappassword"
starttls = 0
</Connection>



<SessionCache>
module = "Memcached"
#memcached = "memcached"
# expire session cache entries
expire = "300"
# wait timeout will be increased each time 50,100,150,... (usec)
lock_wait=50
# abort after n retries
lock_max_retry=50
# session lock times out after (sec)
lock_timeout=10
</SessionCache>



<VirtualHost 12345>
name="accounting"

<Plugin LdapUID>
module="LdapUserConfig"
basedn="c=en"
# sasl_username attribute is uid.
filter_field="sasl_username"
filter="(&(uid=%s)(objectClass=mailRecipient)(objectclass=mtpolicyd)(mailUserStatus=active))"
# copy these fields to current mtpolicyd session
config_fields="mtpolicydMailMessageLimit,mtpolicydMailRecipientLimit"
</Plugin>

<Plugin QuotaUser>
module = "Quota"
time_pattern = "%Y-%m-%d"
field = "sasl_username"
metric = "count"
threshold = 500
# if this field is set it will overwrite the default threshold
uc_threshold = "mtpolicydMailMessageLimit"
# for MSA you may reject, for MTAs you may defer
action = "reject you exceeded your daily message limit"
</Plugin>

<Plugin QuotaUserRecipient>
module = "Quota"
time_pattern = "%Y-%m-%d"
field = "sasl_username"
metric = "count_rcpt"
threshold = 5000
# if this field is set it will overwrite the default threshold
uc_threshold = "mtpolicydMailRecipientLimit"
# for MSA you may reject, for MTAs you may defer
action = "reject you exceeded your daily mail recipient limit"
</Plugin>

<Plugin AcctUser>
module = "Accounting"
fields = "sasl_username"
# Perform day based limit
time_pattern = "%Y-%m-%d"
</Plugin>

</VirtualHost>

0 comments on commit 7599f20

Please sign in to comment.