diff --git a/README.md b/README.md index 8d905c8..e84c187 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,12 @@ Do this as follows: - `realm-management` > `query-groups` - `realm-management` > `query-users` -## Disabling Apricot groups +## Configuring the Apricot LDAP server + +### Anonymous binds + +By default, Apricot allows anonymous queries. +If you would prefer to disable these, please use the `--disable-anonymous-binds` command line option. ### Primary groups diff --git a/apricot/apricot_server.py b/apricot/apricot_server.py index b62a608..4e4659f 100644 --- a/apricot/apricot_server.py +++ b/apricot/apricot_server.py @@ -28,6 +28,7 @@ def __init__( domain: str, port: int, *, + allow_anonymous_binds: bool = True, background_refresh: bool = False, debug: bool = False, enable_mirrored_groups: bool = True, @@ -43,6 +44,7 @@ def __init__( ) -> None: """Initialise an ApricotServer. + @param allow_anonymous_binds: Whether to allow anonymous LDAP binds @param backend: An OAuth backend, @param client_id: An OAuth client ID @param client_secret: An OAuth client secret @@ -125,6 +127,7 @@ def __init__( factory = OAuthLDAPServerFactory( oauth_adaptor, oauth_client, + allow_anonymous_binds=allow_anonymous_binds, background_refresh=background_refresh, refresh_interval=refresh_interval, ) diff --git a/apricot/ldap/oauth_ldap_server_factory.py b/apricot/ldap/oauth_ldap_server_factory.py index 520d7f3..3ac1d7c 100644 --- a/apricot/ldap/oauth_ldap_server_factory.py +++ b/apricot/ldap/oauth_ldap_server_factory.py @@ -17,11 +17,13 @@ def __init__( oauth_adaptor: OAuthDataAdaptor, oauth_client: OAuthClient, *, + allow_anonymous_binds: bool, background_refresh: bool, refresh_interval: int, ) -> None: """Initialise an OAuthLDAPServerFactory. + @param allow_anonymous_binds: Whether to allow anonymous LDAP binds @param background_refresh: Whether to refresh the LDAP tree in the background rather than on access @param oauth_adaptor: An OAuth data adaptor used to construct the LDAP tree @param oauth_client: An OAuth client used to retrieve user and group data @@ -34,6 +36,7 @@ def __init__( background_refresh=background_refresh, refresh_interval=refresh_interval, ) + self.allow_anonymous_binds = allow_anonymous_binds def __repr__(self: Self) -> str: return f"{self.__class__.__name__} using adaptor {self.adaptor}" @@ -46,6 +49,6 @@ def buildProtocol(self: Self, addr: IAddress) -> Protocol: # noqa: N802 @param addr: an object implementing L{IAddress} """ id(addr) # ignore unused arguments - proto = ReadOnlyLDAPServer() + proto = ReadOnlyLDAPServer(allow_anonymous_binds=self.allow_anonymous_binds) proto.factory = self.adaptor return proto diff --git a/apricot/ldap/read_only_ldap_server.py b/apricot/ldap/read_only_ldap_server.py index 285daae..9f1f1c9 100644 --- a/apricot/ldap/read_only_ldap_server.py +++ b/apricot/ldap/read_only_ldap_server.py @@ -30,9 +30,10 @@ class ReadOnlyLDAPServer(LDAPServer): """A read-only LDAP server.""" - def __init__(self: Self) -> None: + def __init__(self: Self, *, allow_anonymous_binds: bool = True) -> None: """Initialise a ReadOnlyLDAPServer.""" super().__init__() + self.allow_anonymous_binds = allow_anonymous_binds self.logger = Logger() def getRootDSE( # noqa: N802 @@ -71,6 +72,9 @@ def handle_LDAPBindRequest( # noqa: N802 """Handle an LDAP bind request.""" try: self.logger.debug("Handling an LDAP bind request.") + if not (self.allow_anonymous_binds or request.dn): + msg = "Anonymous binds are disabled" + raise ValueError(msg) # noqa: TRY301 return super().handle_LDAPBindRequest(request, controls, reply) except Exception as exc: msg = f"LDAP bind request failed. {exc!s}" diff --git a/apricot/oauth/oauth_client.py b/apricot/oauth/oauth_client.py index 77d4c9b..9296c34 100644 --- a/apricot/oauth/oauth_client.py +++ b/apricot/oauth/oauth_client.py @@ -8,6 +8,7 @@ import requests from oauthlib.oauth2 import ( BackendApplicationClient, + InvalidClientIdError, InvalidGrantError, LegacyApplicationClient, TokenExpiredError, @@ -180,7 +181,7 @@ def verify(self: Self, username: str, password: str) -> bool: client_id=self.session_interactive._client.client_id, client_secret=self.client_secret, ) - except InvalidGrantError as exc: + except (InvalidClientIdError, InvalidGrantError) as exc: self.logger.warn( "Authentication failed for user '{user}'. {error}", user=username, diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index a4fbe37..bf40cde 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -9,7 +9,7 @@ EXTRA_OPTS="" # Common server-level options if [ -z "${PORT}" ]; then PORT="1389" - echo "$(date +'%Y-%m-%d %H:%M:%S+0000') [-] PORT environment variable is not set: using default of '${PORT}'" + echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO ] PORT environment variable is not set: using default of '${PORT}'" fi if [ -n "${DEBUG}" ]; then @@ -19,10 +19,14 @@ fi # LDAP tree arguments if [ -z "${DOMAIN}" ]; then - echo "$(date +'%Y-%m-%d %H:%M:%S+0000') [-] DOMAIN environment variable is not set" + echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO ] DOMAIN environment variable is not set" exit 1 fi +if [ -n "${DISABLE_ANONYMOUS_BINDS}" ]; then + EXTRA_OPTS="${EXTRA_OPTS} --disable-anonymous-binds" +fi + if [ -n "${DISABLE_MIRRORED_GROUPS}" ]; then EXTRA_OPTS="${EXTRA_OPTS} --disable-mirrored-groups" fi @@ -38,17 +42,17 @@ fi # OAuth client arguments if [ -z "${BACKEND}" ]; then - echo "$(date +'%Y-%m-%d %H:%M:%S+0000') [-] BACKEND environment variable is not set" + echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO ] BACKEND environment variable is not set" exit 1 fi if [ -z "${CLIENT_ID}" ]; then - echo "$(date +'%Y-%m-%d %H:%M:%S+0000') [-] CLIENT_ID environment variable is not set" + echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO ] CLIENT_ID environment variable is not set" exit 1 fi if [ -z "${CLIENT_SECRET}" ]; then - echo "$(date +'%Y-%m-%d %H:%M:%S+0000') [-] CLIENT_SECRET environment variable is not set" + echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO ] CLIENT_SECRET environment variable is not set" exit 1 fi @@ -72,7 +76,7 @@ fi # Backend arguments: Keycloak if [ -n "${KEYCLOAK_BASE_URL}" ]; then if [ -z "${KEYCLOAK_REALM}" ]; then - echo "$(date +'%Y-%m-%d %H:%M:%S+0000') [-] KEYCLOAK_REALM environment variable is not set" + echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO ] KEYCLOAK_REALM environment variable is not set" exit 1 fi EXTRA_OPTS="${EXTRA_OPTS} --keycloak-base-url $KEYCLOAK_BASE_URL --keycloak-realm $KEYCLOAK_REALM" @@ -86,7 +90,7 @@ fi if [ -n "${REDIS_HOST}" ]; then if [ -z "${REDIS_PORT}" ]; then REDIS_PORT="6379" - echo "$(date +'%Y-%m-%d %H:%M:%S+0000') [-] REDIS_PORT environment variable is not set: using default of '${REDIS_PORT}'" + echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO ] REDIS_PORT environment variable is not set: using default of '${REDIS_PORT}'" fi EXTRA_OPTS="${EXTRA_OPTS} --redis-host $REDIS_HOST --redis-port $REDIS_PORT" fi @@ -95,11 +99,11 @@ fi # TLS arguments if [ -n "${TLS_PORT}" ]; then if [ -z "${TLS_CERTIFICATE}" ]; then - echo "$(date +'%Y-%m-%d %H:%M:%S+0000') [-] TLS_CERTIFICATE environment variable is not set" + echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO ] TLS_CERTIFICATE environment variable is not set" exit 1 fi if [ -z "${TLS_PRIVATE_KEY}" ]; then - echo "$(date +'%Y-%m-%d %H:%M:%S+0000') [-] TLS_PRIVATE_KEY environment variable is not set" + echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO ] TLS_PRIVATE_KEY environment variable is not set" exit 1 fi EXTRA_OPTS="${EXTRA_OPTS} --tls-port $TLS_PORT --tls-certificate $TLS_CERTIFICATE --tls-private-key $TLS_PRIVATE_KEY" diff --git a/run.py b/run.py index 7a655a6..00d2228 100644 --- a/run.py +++ b/run.py @@ -33,6 +33,13 @@ help="Which domain users belong to.", required=True, ) + ldap_group.add_argument( + "--disable-anonymous-binds", + action="store_false", + default=True, + dest="allow_anonymous_binds", + help="Disable anonymous LDAP binds.", + ) ldap_group.add_argument( "--disable-mirrored-groups", action="store_false",