hades.common package

Submodules

hades.common.cli module

Functionality for the Hades command-line utilities in hades.bin.

class hades.common.cli.ArgumentParser(prog=None, usage=None, description=None, epilog=None, parents=[], formatter_class=<class 'argparse.HelpFormatter'>, prefix_chars='-', fromfile_prefix_chars=None, argument_default=None, conflict_handler='error', add_help=True, allow_abbrev=True, exit_on_error=True)[source]

Bases: ArgumentParser

ArgumentParser subclass that exists with os.EX_USAGE exit code if parsing fails.

error(message: string)[source]

Prints a usage message incorporating the message to stderr and exits.

If you override this in a subclass, it should not return -- it should either exit or raise an exception.

class hades.common.cli.VersionAction(option_strings, version_info=None, dest='==SUPPRESS==', default='==SUPPRESS==', help="show program's version number, configure options, copyright notice and exit")[source]

Bases: Action

hades.common.cli.reset_cli_logging()[source]

Reset root logger configuration

hades.common.cli.setup_cli_logging(program, args)[source]

Setup logging for CLI applications, that do not configure logging themselves.

Set log level using command-line options parsed with parser, the HADES_VERBOSITY environment variable or finally the default value DEFAULT_VERBOSITY.

Messages are logged to stderr by default, but can also be logged to syslog.

The possible log level settings are:

  • logging.ERROR is the minimum log level.

  • logging.CRITICAL will always also be logged to STDERR even if logging to syslog.

  • logging.WARNING is the default logging level, but can be suppressed with -q/--quiet or HADES_VERBOSITY=0.

  • Each -v/--verbose increases the verbosity by one level.

When the log level is lower than or equal to logging.DEBUG also the time, the log level and the filename are logged in addition to log message.

Flask and Celery have their own opinionated logging mechanisms. Logging should probably be reset via reset_cli_logging() before handing over control to them.

Parameters:
  • program -- The name of the program

  • args -- The parsed arguments of the program with parser or a subparser.

hades.common.db module

Database utilities.

This module contains

  • the sqlalchemy Table schema definitions

  • structures related to the former, like TypeDecorators

  • functions interacting with the database (both for reading and manipulating)

hades.common.db.as_copy(original_table: Table, new_name: str, temporary: bool = True) Table[source]

Create a copy of a table definition with a different name

Parameters:
  • original_table -- The table to copy

  • new_name -- Name of the new table

  • temporary -- Should the new table be marked as temporary

Returns:

A new table

class hades.common.db.MACAddress(*args, **kwargs)[source]

Bases: TypeDecorator

Custom SQLAlchemy type for MAC addresses.

Use the PostgreSQL macaddr type on the database side and netaddr.EUI on the Python side.

impl

alias of MACADDR

python_type

alias of EUI

process_bind_param(value, dialect)[source]

Receive a bound parameter value to be converted.

Subclasses override this method to return the value that should be passed along to the underlying TypeEngine object, and from there to the DBAPI execute() method.

The operation could be anything desired to perform custom behavior, such as transforming or serializing data. This could also be used as a hook for validating logic.

This operation should be designed with the reverse operation in mind, which would be the process_result_value method of this class.

Parameters:
  • value -- Data to operate upon, of any type expected by this method in the subclass. Can be None.

  • dialect -- the Dialect in use.

process_literal_param(value, dialect)

Receive a literal parameter value to be rendered inline within a statement.

This method is used when the compiler renders a literal value without using binds, typically within DDL such as in the "server default" of a column or an expression within a CHECK constraint.

The returned string will be rendered into the output string.

New in version 0.9.0.

process_result_value(value, dialect)[source]

Receive a result-row column value to be converted.

Subclasses should implement this method to operate on data fetched from the database.

Subclasses override this method to return the value that should be passed back to the application, given a value that is already processed by the underlying TypeEngine object, originally from the DBAPI cursor method fetchone() or similar.

The operation could be anything desired to perform custom behavior, such as transforming or serializing data. This could also be used as a hook for validating logic.

Parameters:
  • value -- Data to operate upon, of any type expected by this method in the subclass. Can be None.

  • dialect -- the Dialect in use.

This operation should be designed to be reversible by the "process_bind_param" method of this class.

hades.common.db.eui_as_unix(mac: EUI) EUI[source]

Represent a mac address as itself with the netaddr.mac_unix_expanded dialect set.

This causes the string representation to be the colon-separated 00:de:ad:be:ef:00 which is used in most other places in hades. This normalization is useful to keep the representations of MAC addresses in the journal consistent.

class hades.common.db.IPAddress(*args, **kwargs)[source]

Bases: TypeDecorator

Custom SQLAlchemy type for IP addresses.

Use the PostgreSQL inet type on the database side and netaddr.IPAddress on the Python side.

impl

alias of INET

python_type

alias of IPAddress

process_bind_param(value, dialect)[source]

Receive a bound parameter value to be converted.

Subclasses override this method to return the value that should be passed along to the underlying TypeEngine object, and from there to the DBAPI execute() method.

The operation could be anything desired to perform custom behavior, such as transforming or serializing data. This could also be used as a hook for validating logic.

This operation should be designed with the reverse operation in mind, which would be the process_result_value method of this class.

Parameters:
  • value -- Data to operate upon, of any type expected by this method in the subclass. Can be None.

  • dialect -- the Dialect in use.

process_literal_param(value, dialect)

Receive a literal parameter value to be rendered inline within a statement.

This method is used when the compiler renders a literal value without using binds, typically within DDL such as in the "server default" of a column or an expression within a CHECK constraint.

The returned string will be rendered into the output string.

New in version 0.9.0.

process_result_value(value, dialect)[source]

Receive a result-row column value to be converted.

Subclasses should implement this method to operate on data fetched from the database.

Subclasses override this method to return the value that should be passed back to the application, given a value that is already processed by the underlying TypeEngine object, originally from the DBAPI cursor method fetchone() or similar.

The operation could be anything desired to perform custom behavior, such as transforming or serializing data. This could also be used as a hook for validating logic.

Parameters:
  • value -- Data to operate upon, of any type expected by this method in the subclass. Can be None.

  • dialect -- the Dialect in use.

This operation should be designed to be reversible by the "process_bind_param" method of this class.

class hades.common.db.TupleArray(item_type, dimensions=None)[source]

Bases: ARRAY

Convenience subclass for ARRAY for zero-indexed tuples.

hades.common.db.alternative_dns
Type:

materialized view

hades.common.db.auth_dhcp_host

The table containing the DHCP host reservations.

Type:

materialized view

hades.common.db.auth_dhcp_lease

The table representing the auth leases. Synced from dnsmasq state via the --dhcp-script hook.

Type:

table

hades.common.db.unauth_dhcp_lease

The table representing the unauth leases. Synced from dnsmasq state via the --dhcp-script hook.

Type:

table

hades.common.db.nas

The table network access switches

Type:

materialized view

hades.common.db.radacct

radius accounting information

Type:

table

hades.common.db.radcheck
Type:

materialized view

hades.common.db.radgroupcheck
Type:

materialized view

hades.common.db.radgroupreply
Type:

materialized view

hades.common.db.radpostauth

Radius Authorization logs

Type:

table

hades.common.db.radreply
Type:

materialized view

hades.common.db.radusergroup
Type:

materialized view

class hades.common.db.utcnow(*clauses, **kwargs)[source]

Bases: FunctionElement

type = DateTime()
hades.common.db.pg_utcnow(element, compiler, **kw)[source]
class hades.common.db.UTCTZInfoFactory(offset: int)[source]

Bases: tzinfo

A tzinfo factory compatible with psycopg2.tz.FixedOffsetTimezone, that checks if the provided UTC offset is zero and returns datetime.timezone.utc. If the offset is not zero an psycopg2.DataError is raised.

This class is implemented as a singleton that always returns the same instance.

class hades.common.db.UTCTZInfoCursorFactory(*args, **kwargs)[source]

Bases: cursor

A Cursor factory that sets the psycopg2.extensions.cursor.tzinfo_factory to UTCTZInfoFactory.

The C implementation of the cursor class does not use the proper Python attribute lookup, therefore we have to set the instance variable rather than use a class attribute.

hades.common.db.create_engine(config: Config, **kwargs)[source]

Set up an engine.

Raises:

UsageError -- if engine fails with sqlalchemy.exc.ArgumentError.

hades.common.db.clean_up_pyroute2_registrations()[source]

Manually remove pyroute2 registrations to psycopg2.extensions.adapters.

a certain version of pyroute2 pollutes the psycopg2 adapters at import time. In particular, lists were forcefully rendered as strings, breaking ARRAY[] usage. We ensure the module is imported and throw out the aforementioned adapters.

hades.common.db.lock_table(connection: Connection, target_table: Table)[source]

Lock a table using a PostgreSQL advisory lock

The OID of the table in the pg_class relation is used as lock id.

Parameters:
  • connection -- DB connection

  • target_table -- Table object

hades.common.db.create_temp_copy(connection: Connection, source: Table, destination: Table)[source]

Create a temporary table as a copy of a source table that will be dropped at the end of the running transaction.

Parameters:
  • connection -- DB connection

  • source -- Source table

  • destination -- Destination table

class hades.common.db.ObjectsDiff(added: List[T], deleted: List[T], modified: List[T])[source]

Bases: Generic[T]

added: List[T]
deleted: List[T]
modified: List[T]
hades.common.db.diff_tables(connection: Connection, master: Table, copy: Table, result_columns: Iterable[Column], unique_columns: Optional[Collection[Column]] = None) ObjectsDiff[Tuple][source]

Compute the differences in the contents of two tables with identical columns.

The master table must have at least one PrimaryKeyConstraint or UniqueConstraint with only non-null columns defined.

If there are multiple constraints defined the constraints that contains the least number of columns are used.

Parameters:
  • connection -- DB connection

  • master -- Master table

  • copy -- Copy of master table

  • result_columns -- columns to return

  • unique_columns -- The columns on which to base the diff. If not specified, will try to make a meaningful decision based on existing table constraints.

Returns:

True, if the contents differ, otherwise False

hades.common.db.refresh_materialized_view(connection: Connection, view: Table)[source]

Execute REFRESH MATERIALIZED VIEW CONCURRENTLY for the given view.

Parameters:
  • connection -- A valid SQLAlchemy connection

  • view -- The view to refresh

hades.common.db.refresh_and_diff_materialized_view(connection: Connection, view: Table, copy: Table, result_columns: Iterable[Column], unique_columns: Optional[Collection[Column]] = None) ObjectsDiff[Tuple][source]

Lock the given view with an advisory lock, create a temporary table of the view, refresh the view and compute the difference.

Parameters:
  • connection -- A valid SQLAlchemy connection

  • view -- The view to refresh and diff

  • copy -- A temporary table to create and diff

  • result_columns -- The columns to return

  • unique_columns -- The columns on which to base the diff. If not specified, will try to make a meaningful decision based on existing table constraints.

Returns:

A 3-tuple containing three lists of tuples of the result_columns of added, deleted and modified records due to the refresh.

hades.common.db.delete_old_sessions(connection: Connection, interval: timedelta)[source]

Delete old session from the radacct table.

hades.common.db.delete_old_auth_attempts(connection: Connection, interval: timedelta)[source]

Delete old authentication results from the radpostauth table.

hades.common.db.get_groups(connection: Connection, mac: EUI) Iterator[Tuple[IPAddress, str, str]][source]

Get the groups of a user.

Parameters:
  • connection -- A SQLAlchemy connection

  • mac -- MAC address

Returns:

An iterator that yields (NAS-IP-Address, NAS-Port-Id, Group-Name)- tuples

hades.common.db.get_latest_auth_attempt(connection: Connection, mac: EUI) Optional[Tuple[IPAddress, str, str, Tuple[str, ...], Tuple[Tuple[str, str], ...], datetime]][source]

Get the latest auth attempt of a MAC address that occurred within twice the reauthentication interval.

Parameters:
  • connection -- A SQLAlchemy connection

  • mac (str) -- MAC address

Returns:

A (NAS-IP-Address, NAS-Port-Id, Packet-Type, Groups, Reply, Auth-Date) tuple or None if no attempt was found. Groups is an tuple of group names and Reply is a tuple of (Attribute, Value)-pairs that were sent in Access-Accept responses.

hades.common.db.get_all_auth_dhcp_hosts(connection: Connection) Iterator[Tuple[EUI, IPAddress, Optional[str]]][source]

Return all DHCP host configurations.

Parameters:

connection -- A SQLAlchemy connection

Returns:

An iterator that yields (mac, ip, hostname)-tuples

hades.common.db.get_all_nas_clients(connection: Connection) Iterator[Tuple[str, str, str, int, str, str, str, str]][source]

Return all NAS clients.

Parameters:

connection -- A SQLAlchemy connection

Returns:

An iterator that yields (shortname, nasname, type, ports, secret, server, community, description)-tuples

hades.common.db.get_sessions_of_mac(connection: Connection, mac: EUI, when: Optional[Tuple[Optional[datetime], Optional[datetime]]] = None, limit: Optional[int] = None) Iterator[Tuple[IPAddress, str, datetime, datetime]][source]

Return accounting sessions of a particular MAC address ordered by Session-Start-Time descending.

Parameters:
  • connection -- A SQLAlchemy connection

  • mac (str) -- MAC address

  • when -- Range in which Session-Start-Time must be within

  • limit -- Maximum number of records

Returns:

An iterator that yields (NAS-IP-Address, NAS-Port-Id, Session-Start-Time, Session-Stop-Time)-tuples ordered by Session-Start-Time descending

hades.common.db.get_auth_attempts_of_mac(connection: Connection, mac: EUI, when: Optional[Tuple[Optional[datetime], Optional[datetime]]] = None, limit: Optional[int] = None) Iterator[Tuple[IPAddress, str, str, Tuple[str, ...], Tuple[Tuple[str, str], ...], datetime]][source]

Return auth attempts of a particular MAC address order by Auth-Date descending.

Parameters:
  • connection -- A SQLAlchemy connection

  • mac -- MAC address

  • when -- Range in which Auth-Date must be within

  • limit -- Maximum number of records

Returns:

An iterator that yields (NAS-IP-Address, NAS-Port-Id, Packet-Type, Groups, Reply, Auth-Date)-tuples ordered by Auth-Date descending

hades.common.db.get_auth_attempts_at_port(connection: Connection, nas_ip_address: IPAddress, nas_port_id: str, when: Optional[Tuple[Optional[datetime], Optional[datetime]]] = None, limit: Optional[int] = None) Iterator[Tuple[str, str, Tuple[str, ...], Tuple[Tuple[str, str], ...], datetime]][source]

Return auth attempts at a particular port of an NAS ordered by Auth-Date descending.

Parameters:
  • connection -- A SQLAlchemy connection

  • nas_ip_address -- NAS IP address

  • nas_port_id -- NAS Port ID

  • when -- Range in which Auth-Date must be within

  • limit -- Maximum number of records

Returns:

An iterator that yields (User-Name, Packet-Type, Groups, Reply, Auth-Date)-tuples ordered by Auth-Date descending

hades.common.db.get_all_alternative_dns_ips(connection: Connection) Iterator[IPAddress][source]

Return all IPs for alternative DNS configuration.

Parameters:

connection -- A SQLAlchemy connection

Returns:

An iterator that yields ip addresses

hades.common.db.get_all_dhcp_leases(dhcp_lease_table: Table, connection: Connection, subnet: Optional[IPNetwork] = None, limit: Optional[int] = None, interval: Optional[timedelta] = None) Iterator[Tuple[datetime, EUI, IPAddress, Optional[str], Optional[bytes]]][source]

Return all dhcp leases.

Parameters:
  • dhcp_lease_table -- DHCP lease table

  • connection -- A SQLAlchemy connection

  • subnet -- Limit leases to subnet

  • limit -- Maximum number of leases

  • interval -- If set, only return leases older than now - interval.

Returns:

An iterator that yields (Expires-At, MAC, IP-Address, Hostname, Client-ID)-tuples

hades.common.db.get_dhcp_lease_of_ip(dhcp_lease_table: Table, connection: Connection, ip: IPAddress) Optional[Tuple[datetime, EUI, Optional[str], Optional[bytes]]][source]

Get basic lease information for a given IP.

Parameters:
  • dhcp_lease_table -- DHCP lease table

  • connection -- A SQLAlchemy connection

  • ip -- IP address

Returns:

An (Expiry-Time, MAC, Hostname, Client-ID)-tuple or None

hades.common.db.get_dhcp_leases_of_mac(dhcp_lease_table: Table, connection: Connection, mac: EUI) Iterator[Tuple[datetime, IPAddress, Optional[str], Optional[bytes]]][source]

Get basic information about all leases of a given MAC.

Parameters:
  • dhcp_lease_table -- DHCP lease table

  • connection -- A SQLAlchemy connection

  • mac -- MAC address

Returns:

An iterator of (Expiry-Time, IP-Address, Hostname, Client-ID)-tuples ordered by Expiry-Time descending

hades.common.db.get_all_auth_dhcp_leases(connection: Connection, subnet: Optional[IPNetwork] = None, limit: Optional[int] = None, interval: Optional[timedelta] = None) Iterator[Tuple[datetime, EUI, IPAddress, Optional[str], Optional[bytes]]][source]

Return all auth leases.

Parameters:
  • connection -- A SQLAlchemy connection

  • subnet -- Limit leases to subnet

  • limit -- Maximum number of leases

  • interval -- If set, only return leases older than now - interval.

Returns:

An iterator that yields (Expires-At, MAC, IP-Address, Hostname, Client-ID)-tuples

hades.common.db.get_auth_dhcp_lease_of_ip(connection: Connection, ip: IPAddress) Optional[Tuple[datetime, EUI, Optional[str], Optional[bytes]]][source]

Get basic auth lease information for a given IP.

Parameters:
  • connection -- A SQLAlchemy connection

  • ip -- IP address

Returns:

An iterator of (Expiry-Time, MAC, Hostname, Client-ID)-tuples or None

hades.common.db.get_auth_dhcp_leases_of_mac(connection: Connection, mac: EUI) Iterator[Tuple[datetime, IPAddress, Optional[str], Optional[bytes]]][source]

Get basic information about all auth leases of a given MAC.

Parameters:
  • connection -- A SQLAlchemy connection

  • mac -- MAC address

Returns:

An iterator of (Expiry-Time, IP-Address, Hostname, Client-ID)-tuples ordered by Expiry-Time descending

hades.common.db.get_all_unauth_dhcp_leases(connection: Connection, subnet: Optional[IPNetwork] = None, limit: Optional[int] = None, interval: Optional[timedelta] = None) Iterator[Tuple[datetime, EUI, IPAddress, Optional[str], Optional[bytes]]][source]

Return all unauth leases

Parameters:
  • connection -- A SQLAlchemy connection

  • subnet -- Limit leases to subnet

  • limit -- Maximum number of leases

  • interval -- If set, only return leases older than now - interval.

Returns:

An iterator that yields (Expires-At, MAC, IP-Address, Hostname, Client-ID)-tuples

hades.common.db.get_unauth_dhcp_lease_of_ip(connection: Connection, ip: IPAddress) Optional[Tuple[datetime, EUI, Optional[str], Optional[bytes]]][source]

Get basic unauth lease information for a given IP.

Parameters:
  • connection -- A SQLAlchemy connection

  • ip -- IP address

Returns:

An (Expiry-Time, MAC, Hostname, Client-ID)-tuple or None

hades.common.db.get_unauth_dhcp_leases_of_mac(connection: Connection, mac: EUI) Iterator[Tuple[datetime, IPAddress, Optional[str], Optional[bytes]]][source]

Get basic information about all unauth leases of a given MAC.

Parameters:
  • connection -- A SQLAlchemy connection

  • mac -- MAC address

Returns:

An iterator of (Expiry-Time, IP-Address, Hostname, Client-ID) tuples ordered by Expiry-Time descending

class hades.common.db.LeaseInfo(ip: netaddr.ip.IPAddress, mac: netaddr.eui.EUI)[source]

Bases: object

ip: IPAddress
mac: EUI
hades.common.db.get_all_invalid_auth_dhcp_leases(connection: Connection) Iterator[LeaseInfo][source]

Get all auth DHCP leases which do not belong to a host reservation as given in auth_dhcp_lease.

Parameters:

connection -- A SQLAlchemy connection

Returns:

an iterator of (IPAddress, MAC) tuples.

hades.common.glib module

exception hades.common.glib.DBusError(exc: GError)[source]

Bases: TypedGLibError

Indicates an error during a DBus operation.

domain: int = 125
exception hades.common.glib.DBusErrorNoReply(exc: GError)[source]

Bases: DBusError, DBusTimeout

code: DBusError = <enum G_DBUS_ERROR_NO_REPLY of type Gio.DBusError>
exception hades.common.glib.DBusErrorServiceUnknown(exc: GError)[source]

Bases: DBusError

The bus doesn't know how to launch a service to supply the bus name you wanted.

code: DBusError = <enum G_DBUS_ERROR_SERVICE_UNKNOWN of type Gio.DBusError>
exception hades.common.glib.DBusErrorTimedOut(exc: GError)[source]

Bases: DBusError, DBusTimeout

code: DBusError = <enum G_DBUS_ERROR_TIMED_OUT of type Gio.DBusError>
exception hades.common.glib.DBusErrorTimeout(exc: GError)[source]

Bases: DBusError, DBusTimeout

code: DBusError = <enum G_DBUS_ERROR_TIMEOUT of type Gio.DBusError>
exception hades.common.glib.DBusErrorUnknownObject(exc: GError)[source]

Bases: DBusError

Object you invoked a method on isn’t known.

code: DBusError = <enum G_DBUS_ERROR_UNKNOWN_OBJECT of type Gio.DBusError>
exception hades.common.glib.DBusTimeout[source]

Bases: Exception

Indicates a timeout during a DBus operation.

exception hades.common.glib.TypedGLibError(exc: GError)[source]

Bases: Exception

Base GLib exception

code: DBusError = None
codes: Dict[DBusError, Type[TypedGLibError]] = {<enum G_DBUS_ERROR_SERVICE_UNKNOWN of type Gio.DBusError>: <class 'hades.common.glib.DBusErrorServiceUnknown'>, <enum G_DBUS_ERROR_NO_REPLY of type Gio.DBusError>: <class 'hades.common.glib.DBusErrorNoReply'>, <enum G_DBUS_ERROR_TIMEOUT of type Gio.DBusError>: <class 'hades.common.glib.DBusErrorTimeout'>, <enum G_DBUS_ERROR_TIMED_OUT of type Gio.DBusError>: <class 'hades.common.glib.DBusErrorTimedOut'>, <enum G_DBUS_ERROR_UNKNOWN_OBJECT of type Gio.DBusError>: <class 'hades.common.glib.DBusErrorUnknownObject'>}
domain: int = None
domains: Dict[int, Type[TypedGLibError]] = {125: <class 'hades.common.glib.DBusError'>}
classmethod from_code(exc: GError)[source]
classmethod from_exception(exc: GError)[source]

Convert untyped GLib.Error exceptions into a proper exception hierarchy based on domain and code of the error.

hades.common.glib.typed_glib_error()[source]

hades.common.privileges module

hades.common.privileges.drop_privileges(passwd, group)[source]

Drop privileges completely

hades.common.privileges.dropped_privileges(passwd: struct_passwd)[source]

Context manager for temporarily switching real and effective UID and real and effective GID.