chapps.policy module

Policy managers

All email policy managers inherit from EmailPolicy, which provides a fair amount of base functionality useful to its subclasses. So far, all but the SPFEnforcementPolicy are contained here. That one has extra dependencies which are thus kept isolated. Find it in spf_policy.

class chapps.policy.EmailPolicy[source]

Bases: object

Abstract policy manager

Subclasses must:

This abstract superclass provides a standard framework for constructing Redis keys, since the main purpose of the policy manager is to make decisions about email by consulting Redis.

Instance attributes:

config

chapps.config.CHAPPSConfig either passed in during initialization or inherited from the environment

params

chapps.util.AttrDict corresponding to the config for the policy manager

sentinel

a redis.Sentinel handle, or None if using only Redis

redis

a redis.Redis handle

redis_key_prefix = 'chapps'

A placeholder value, since this class is abstract

Subclasses should set this to a unique prefix identifying the policy manager. For examples, see the included subclasses.

static rediskey(prefix, *args)[source]

Format a string to serve as a Redis key for arbitrary data

Parameters
  • prefix (str) – a prefix unique to the policy manager (subclass)

  • args (List[str]) – a list of strings to use to construct the rest of the key

In CHAPPS, each policy has its own prefix. What other data the policy uses to construct the key is not relevant to any other entities, though it must be sent as a string.

This routine simply joins up all the tokens with colon (:) characters, so it is not recommended to use them as part of the key-components (although it should ‘just work’).

classmethod _fmtkey(*args)[source]

Convenience classmethod for Redis key construction

Parameters

args (List[str]) – a list of key components

Subclasses may use this method which automatically discovers the prefix.

__init__(cfg=None)[source]

Sets up a new policy manager

Parameters

cfg (chapps.config.CHAPPSConfig) – optional CHAPPSConfig object for config override

Store the config and get the params for the specific policy class, which are in a config block named for the class. Using that config, set the policy manager up with a Redis handle, and an instance cache (from expiring_dict.ExpiringDict)

_adapter_handle()[source]

Context manager for obtaining a database handle

In order to acquire policy configuration data, the policy manager must be able to reach the RDBMS or other policy-config data store. One of the policy manager’s priciple jobs is to obtain this data from the database and stuff it into Redis for future reference.

Adapter configuration, in terms of how to access the database, is obtained from the config object.

approve_policy_request(ppr, **opts)[source]

Determine a policy outcome based on the PPR provided

This routine may return a boolean PASS/FAIL response, or it may for some policy classes return a string, which represents the policy outcome and is suitable for sending to Postfix.

The result of the policy approval is cached based on the instance value provided by Postfix. The memoization is done here in the superclass in order to avoid duplication of memoization code.

Return type

Union[str, bool]

Parameters

ppr (chapps.util.PostfixPolicyRequest) –

class chapps.policy.PostfixActions[source]

Bases: object

Superclass for Postfix action adapters

static dunno(*args, **kwargs)[source]

Return the Postfix directive DUNNO

static okay(*args, **kwargs)[source]

Return the Postfix directive OK

static ok(*args, **kwargs)

ok() is an alias for okay()

static defer_if_permit(msg, *args, **kwargs)[source]

Return the Postfix DEFER_IF_PERMIT directive with the provided message

static reject(msg, *args, **kwargs)[source]

Return the Postfix REJECT directive along with the provided message

static prepend(*args, **kwargs)[source]

Return the Postfix PREPEND directive. Include the header to prepend as keyword-argument prepend

__init__(cfg=None)[source]

Optionally supply a chapps.config.CHAPPSConfig instance as the first argument.

action_for(*args, **kwargs)[source]

Abstract method which must be implemented in subclasses.

This method is intended to map responses from a policy manager onto Postfix directives.

Not all policy managers return only yes/no answers. Some, like chapps.spf_policy.SPFEnforcementPolicy, return a handful of different possible values, and so there must be a mechanism for allowing sites to determine what happens when each of those different outcomes occurs.

class chapps.policy.PostfixPassfailActions[source]

Bases: chapps.policy.PostfixActions

Postfix Actions adapter for PASS/FAIL policy responses.

Many policies return True if the email should be accepted/forwarded and return False if the email should be rejected/dropped. This class encapsulates the common case, and includes some logic to extract precise instructions from the config.

__init__(cfg=None)[source]

Optionally supply a chapps.config.CHAPPSConfig instance as the first argument.

action_for(pf_result)[source]

Return an action closure for a pass/fail policy

Evaluates its argument pf_result as a boolean and returns the action closure for ‘passing’ if True, otherwise the action closure for ‘fail’. To provide backwards-compatibility with older versions, and to allow for more descriptive configuration elements, the actions may be attached to keys named acceptance_message or rejection_message instead of passing and fail respectively. This is only true of policies with action factories inheriting from PostfixPassfailActions

class chapps.policy.PostfixOQPActions[source]

Bases: chapps.policy.PostfixPassfailActions

Postfix Action translator for chapps.policy.OutboundQuotaPolicy

__init__(cfg=None)[source]

Optionally provide an instance of :py::class`chapps.config.CHAPPSConfig`.

All this class does is wire up self.config to point at the chapps.policy.OutboundQuotaPolicy config block.

class chapps.policy.PostfixGRLActions[source]

Bases: chapps.policy.PostfixPassfailActions

Postfix Action translator for chapps.policy.GreylistingPolicy

__init__(cfg=None)[source]

Optionally provide an instance of chapps.config.CHAPPSConfig.

All this class does is wire up self.config to point at the chapps.policy.GreylistingPolicy config block.

class chapps.policy.InboundPolicy[source]

Bases: chapps.policy.EmailPolicy

adapter_class

alias of chapps.sqla_adapter.SQLAInboundFlagsAdapter

domain_option_key(ppr)[source]

Return the Redis key for the domain’s Greylisting option

Uses the first of the list of tokenized recipients. Generally, inbound mail is expected to contain only one recipient per email.

Parameters

ppr (chapps.inbound.InboundPPR) –

class chapps.policy.GreylistingPolicy[source]

Bases: chapps.policy.InboundPolicy

Policy manager which implements greylisting

Greylisting is a well-defined and frequently-implemented pattern. This implementation stores the tracking information in Redis.

Instance attributes (in addition to those of EmailPolicy):

min_defer

minimum time between retries, in seconds

cache_ttl

how long to store tracking data, in seconds

allow_after

success threshold, after which the client may be whitelisted

redis_key_prefix = 'grl'

Greylisting Redis key prefix

__init__(cfg=None, *, minimum_deferral=60, cache_ttl=86400, auto_allow_after=None)[source]

Initialize a greylisting policy manager

Parameters
  • cfg (chapps.config.CHAPPSConfig) – optional config override

  • minimum_deferral (int) – min time between retries, in seconds

  • cache_ttl (int) – tracking data cache expiration time in seconds

  • auto_allow_after (int) – number of successful attempts needed before source client is considered trusted, and no longer incurs deferrals

tuple_key(ppr)[source]

Return the greylisting tuple as a Redis key

The names of the values taken from ppr are as follows (in order):

  • client_address

  • sender

  • recipient

Return type

str

Parameters

ppr (chapps.util.PostfixPolicyRequest) –

client_key(ppr)[source]

Return the greylisting client key

This key indicates whether the client has enough successful resubmissions to be whitelisted.

Parameters

ppr (chapps.util.PostfixPolicyRequest) –

acquire_policy_for(ppr)[source]
Parameters

ppr (chapps.inbound.InboundPPR) –

_approve_policy_request(ppr, **opts)[source]

Perform greylisting

Parameters

ppr (chapps.inbound.InboundPPR) –

class chapps.policy.OutboundQuotaPolicy[source]

Bases: chapps.policy.EmailPolicy

Policy manager which implements an outbound quota limitation

Outbound email is controlled based on the count of (attempted) transmissions in the last 24 hours. Some parameters are provided to fine-tune the behavior of the limiting algorithm.

Instance attributes (in addition to those of EmailPolicy):

interval

number of seconds to store transmission attemps, and to use for quota evaluation; defaults to one day

counting_recipients

a boolean determined from the config; whether to count each recipient of a multi-recipient email as a separate transmission for quota purposes

min_delta

defaults to 0; if set, the number of seconds which must elapse between send attempts. Currently experimental

adapter_class

alias of chapps.sqla_adapter.SQLAQuotaAdapter

redis_key_prefix = 'oqp'

OutboundQuotaPolicy Redis prefix

__init__(cfg=None, *, enforcement_interval=None, min_delta=0)[source]

Set up an outbound quota policy manager

Parameters
  • cfg (chapps.config.CHAPPSConfig) – optional config override

  • enforcement_interval (int) – number of seconds to store transmission attemps, and to use for quota evaluation; defaults to one day

  • min_delta (int) – Minimum time which must pass between transmission attempts; defaults to 5 seconds to prevent spamming. Set to 0 to disable

current_quota(user, quota=None)[source]

Provide real-time remaining quota for a user

Parameters
Returns

(remaining quota count, [remarks,…])

Return type

Tuple[int, List[str]]

The caller is anticipated to be the API. The User and Quota are both available, so the Quota may be provided, but it is not required. The user parameter is expected to contain a string at present, though this may change as the pydantic data models become more tightly integrated into the codebase.

The return value, intended for wrapping by the API and transmission to a client, is a tuple composed of:

  1. the number of transmission attempts remaining to the user at the moment the query executed

  2. a list of remarks created by the inspection routine

reset_quota(user)[source]

Reset quota for user

Parameters

user (str) – user-identifier

Returns

(number of records dropped, [remarks,…])

Return type

Tuple[int,List[str]]

This method is intended for real-time management of the Redis configuration mirror. It will drop all the attempts from the outbound-quota transmission-tracking list for the named user.

refresh_policy_cache(user, quota)[source]

API adapter method for refreshing policy config cache

Parameters
acquire_policy_for(user, quota=None)[source]

Populate Redis with policy config data for a user

Parameters
  • user (str) – user-identifier

  • quota (int) – optional quota to load for the user. This is provided mainly to optimize actions taken by the API.

Go get the policy for a sender from the policy adapter.

If the margin needs to be configured on a per-sender basis, this is the place to adjust that. Right now, the margin is set in the config file, and applied to each user as policy config is loaded.

_approve_policy_request(ppr)[source]

Determine whether this email falls within the quota

Parameters

ppr (chapps.outbound.OutboundPPR) – the Postfix payload

Returns True if this email is within the quota.

This routine implements memoization on ppr.instance in order to overcome the Postfix double-checking weirdness. Sometimes, Postfix sends a request about a given email twice, but this is easy to spot because they will have the same value for ppr.instance.

Parameters

ppr (chapps.outbound.OutboundPPR) –

class chapps.policy.SenderDomainAuthPolicy[source]

Bases: chapps.policy.EmailPolicy

Policy manager implementing domain and whole-email matching for senders

This class encapsulates explicit policy regarding what sorts of masquerading authenticated users are allowed to do. Currently, two sorts of matches are handled, in succession.

First, the domain part of the email address, the entire string after the @, is matched against Domain entries linked to the User.

If there is no Domain match, then Email entries linked to the User are checked. Email entries must match the entirety of a policy request’s sender attribute in order to pass.

adapter_class

alias of chapps.sqla_adapter.SQLASenderDomainAuthAdapter

redis_key_prefix = 'sda'

Sender domain auth Redis key prefix

__init__(cfg=None)[source]

Set up a new sender domain authorization policy manager

Parameters

cfg (chapps.config.CHAPPSConfig) – optional config override

sender_domain_key(ppr)[source]

Create a Redis key for a user->domain mapping

Parameters

ppr (chapps.outbound.OutboundPPR) – a Postfix payload

Returns

the sender domain key, by obtaining the domain part of the email address from ppr.sender

Return type

str

sender_email_key(ppr)[source]

Create a Redis key for a user->email mapping

Parameters

ppr (chapps.outbound.OutboundPPR) – a Postfix payload

Returns

the sender email key, by obtaining the email address from ppr.sender

Return type

str

_sender_domain_key(user, domain)[source]

Passes its two string params to _fmtkey

Parameters
  • user (str) – user-identifier

  • domain (str) – origin domain or email address

Returns

a Redis key

Return type

str

Should be called _sender_auth_key since it works with both domains and email addresses.

_get_sender_domain(ppr)[source]

Returns the domain portion of ppr.sender

Parameters

ppr (chapps.outbound.OutboundPPR) – a Postfix payload

Returns

the domain part of ppr.sender

Return type

str

Raises
acquire_policy_for(ppr)[source]

Populate Redis with policy config

Parameters

ppr (chapps.outbound.OutboundPPR) – a Postfix payload

Returns

whether the policy allows ppr

Return type

bool

Populates Redis and return the policy result for ppr.

check_policy_cache(user, domain)[source]

Check a particular policy cache entry for the API

Parameters
  • user (str) – user-identifier

  • domain (str) – domain or email to check

Returns

the cached policy

Return type

chapps.models.SDAStatus

clear_policy_cache(user, domain)[source]

Clear a specific policy cache entry

Parameters
  • user (str) – user-identifier

  • domain (str) – domain or email to clear

Returns

the previous policy

Return type

SDAStatus

bulk_clear_policy_cache(users, domains=None, emails=None)[source]

Clear SDA policy cache for Users x [Domains + Users]

Parameters
  • users (List[str]) – a list of user-identifiers

  • domains (Optional[List[str]]) – a list of domain names

  • emails (Optional[List[str]]) – a list of email addresses

Return type

None

bulk_check_policy_cache(users, domains=None, emails=None)[source]

Map auth subject onto user status

Parameters
  • users (List[str]) – a list of user-identifiers

  • domains (Optional[List[str]]) – a list of domain names

  • emails (Optional[List[str]]) – a list of email addresses

Returns

an auth subject => user => status map as described below

Return type

Dict[str, Dict[str, SDAStatus]]

Builds a map keyed on auth subject (Domain and/or Email), full of maps from username to status. It looks a bit like this:

Mainly intended for use by the API.