From e7e3cdbab01cf38c75e998270d51ac57030ab97b Mon Sep 17 00:00:00 2001 From: Felix Gustavsson Date: Sun, 19 May 2024 19:45:51 +0200 Subject: [PATCH 1/8] * Add support for TLS * Add support for refreshing the database periodically in the background * Add support to set refresh interval used for periodical refresh and for how often it should be refreshed on demand --- Dockerfile | 2 +- README.md | 12 +++++++++ apricot/apricot_server.py | 30 +++++++++++++++++++---- apricot/ldap/oauth_ldap_server_factory.py | 4 +-- apricot/ldap/oauth_ldap_tree.py | 16 ++++++++---- docker/docker-compose.yaml | 1 + docker/entrypoint.sh | 22 +++++++++++++++++ run.py | 12 ++++++++- 8 files changed, 85 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6f8fe57..b8940f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ COPY ./run.py . RUN chmod ugo+x ./entrypoint.sh # Open appropriate ports -EXPOSE 1389 +EXPOSE 1389, 1636 # Run the server ENTRYPOINT ["./entrypoint.sh"] diff --git a/README.md b/README.md index 170917d..348dc60 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,18 @@ from the `docker` directory. You can use a Redis server to store generated `uidNumber` and `gidNumber` values in a more persistent way. To do this, you will need to provide the `--redis-host` and `--redis-port` arguments to `run.py`. +### Configure background refresh [Optional] + +By default Apricot will refresh on demand when the data is older than 60 seconds. +If it takes a long time to fetch all users and groups, or you want to ensure that each request prompty gets a respose, you may want to configure background refresh to have it periodically be refreshed in the background. + +This is enabled with the `--background-refresh` flag, which uses the `--refresh-interval=60` parameter as the interval to refresh the ldap database. + +### Using TLS [Optional] + +You can set up a TLS listener to communicate with encryption enabled over the configured port. +To enable it you need to configure the tls port ex. `--tls-port=1636`, and provide a path to the pem files for the certificate `--tls-certificate=` and the private key `--tls-private-key=`. + ## Outputs This will create an LDAP tree that looks like this: diff --git a/apricot/apricot_server.py b/apricot/apricot_server.py index fa98c22..6d9f015 100644 --- a/apricot/apricot_server.py +++ b/apricot/apricot_server.py @@ -1,9 +1,9 @@ import inspect import sys -from typing import Any, cast +from typing import Any, cast, Optional -from twisted.internet import reactor -from twisted.internet.endpoints import serverFromString +from twisted.internet import reactor, task +from twisted.internet.endpoints import serverFromString, quoteStringArgument from twisted.internet.interfaces import IReactorCore, IStreamServerEndpoint from twisted.python import log @@ -21,10 +21,15 @@ def __init__( domain: str, port: int, *, + background_refresh: bool = False, debug: bool = False, enable_mirrored_groups: bool = True, redis_host: str | None = None, redis_port: int | None = None, + refresh_interval: int = 60, + tls_port: Optional[int] = None, + tls_certificate: Optional[str] = None, + tls_private_key: Optional[str] = None, **kwargs: Any, ) -> None: self.debug = debug @@ -66,15 +71,30 @@ def __init__( if self.debug: log.msg("Creating an LDAPServerFactory.") factory = OAuthLDAPServerFactory( - domain, oauth_client, enable_mirrored_groups=enable_mirrored_groups + domain, oauth_client, background_refresh=background_refresh, enable_mirrored_groups=enable_mirrored_groups, refresh_interval=refresh_interval ) + if background_refresh: + if self.debug: + log.msg(f"Starting background refresh (interval={factory.adaptor.refresh_interval})") + loop = task.LoopingCall(factory.adaptor.refresh) + loop.start(factory.adaptor.refresh_interval) + # Attach a listening endpoint if self.debug: - log.msg("Attaching a listening endpoint.") + log.msg("Attaching a listening endpoint (plain).") endpoint: IStreamServerEndpoint = serverFromString(reactor, f"tcp:{port}") endpoint.listen(factory) + # Attach a listening endpoint + if tls_port: + if not (tls_certificate or tls_private_key): + raise ValueError("No TLS certificate or private key provided. Make sure you provide these parameters or disable TLS by not providing the TLS port") + if self.debug: + log.msg("Attaching a listening endpoint (TLS).") + ssl_endpoint: IStreamServerEndpoint = serverFromString(reactor, f"ssl:{tls_port}:privateKey={quoteStringArgument(tls_private_key)}:certKey={quoteStringArgument(tls_certificate)}") + ssl_endpoint.listen(factory) + # Load the Twisted reactor self.reactor = cast(IReactorCore, reactor) diff --git a/apricot/ldap/oauth_ldap_server_factory.py b/apricot/ldap/oauth_ldap_server_factory.py index bcabc6c..3a61578 100644 --- a/apricot/ldap/oauth_ldap_server_factory.py +++ b/apricot/ldap/oauth_ldap_server_factory.py @@ -9,7 +9,7 @@ class OAuthLDAPServerFactory(ServerFactory): def __init__( - self, domain: str, oauth_client: OAuthClient, *, enable_mirrored_groups: bool + self, domain: str, oauth_client: OAuthClient, *, background_refresh: bool, enable_mirrored_groups: bool, refresh_interval: int, ): """ Initialise an LDAPServerFactory @@ -18,7 +18,7 @@ def __init__( """ # Create an LDAP lookup tree self.adaptor = OAuthLDAPTree( - domain, oauth_client, enable_mirrored_groups=enable_mirrored_groups + domain, oauth_client, background_refresh=background_refresh, enable_mirrored_groups=enable_mirrored_groups, refresh_interval=refresh_interval ) def __repr__(self) -> str: diff --git a/apricot/ldap/oauth_ldap_tree.py b/apricot/ldap/oauth_ldap_tree.py index d9eb133..9d11023 100644 --- a/apricot/ldap/oauth_ldap_tree.py +++ b/apricot/ldap/oauth_ldap_tree.py @@ -18,8 +18,9 @@ def __init__( domain: str, oauth_client: OAuthClient, *, + background_refresh: bool, enable_mirrored_groups: bool, - refresh_interval: int = 60, + refresh_interval, ) -> None: """ Initialise an OAuthLDAPTree @@ -28,13 +29,14 @@ def __init__( @param oauth_client: An OAuth client used to construct the LDAP tree @param refresh_interval: Interval in seconds after which the tree must be refreshed """ + self.background_refresh = background_refresh self.debug = oauth_client.debug self.domain = domain + self.enable_mirrored_groups = enable_mirrored_groups self.last_update = time.monotonic() self.oauth_client = oauth_client self.refresh_interval = refresh_interval self.root_: OAuthLDAPEntry | None = None - self.enable_mirrored_groups = enable_mirrored_groups @property def dn(self) -> DistinguishedName: @@ -47,9 +49,14 @@ def root(self) -> OAuthLDAPEntry: @return: An OAuthLDAPEntry for the tree """ + if not self.background_refresh: + self.refresh() + return self.root_ + + def refresh(self): if ( - not self.root_ - or (time.monotonic() - self.last_update) > self.refresh_interval + not self.root_ + or (time.monotonic() - self.last_update) > self.refresh_interval ): # Update users and groups from the OAuth server log.msg("Retrieving OAuth data.") @@ -104,7 +111,6 @@ def root(self) -> OAuthLDAPEntry: # Set last updated time log.msg("Finished building LDAP tree.") self.last_update = time.monotonic() - return self.root_ def __repr__(self) -> str: return f"{self.__class__.__name__} with backend {self.oauth_client.__class__.__name__}" diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 3824a35..7ce3b01 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -14,6 +14,7 @@ services: REDIS_HOST: "redis" ports: - "1389:1389" + - "1636:1636" restart: always redis: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 04261da..bd96ceb 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -41,10 +41,32 @@ if [ -n "${DISABLE_MIRRORED_GROUPS}" ]; then EXTRA_OPTS="${EXTRA_OPTS} --disable-mirrored-groups" fi +if [ -n "${BACKGROUND_REFRESH}" ]; then + EXTRA_OPTS="${EXTRA_OPTS} --background-refresh" +fi + +if [ -n "${REFRESH_INTERVAL}" ]; then + EXTRA_OPTS="${EXTRA_OPTS} --refresh-interval $REFRESH_INTERVAL" +fi + if [ -n "${ENTRA_TENANT_ID}" ]; then EXTRA_OPTS="${EXTRA_OPTS} --entra-tenant-id $ENTRA_TENANT_ID" fi + + +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" + 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" + exit 1 + fi + EXTRA_OPTS="${EXTRA_OPTS} --tls-port $TLS_PORT --tls-certificate $TLS_CERTIFICATE --tls-private-key $TLS_PRIVATE_KEY" +fi + if [ -n "${REDIS_HOST}" ]; then if [ -z "${REDIS_PORT}" ]; then REDIS_PORT="6379" diff --git a/run.py b/run.py index c228f20..3fbe7a6 100644 --- a/run.py +++ b/run.py @@ -16,10 +16,15 @@ parser.add_argument("-i", "--client-id", type=str, help="OAuth client ID.") parser.add_argument("-p", "--port", type=int, default=1389, help="Port to run on.") parser.add_argument("-s", "--client-secret", type=str, help="OAuth client secret.") + parser.add_argument("--background-refresh", action="store_true", default=False, + help="Refresh in the background instead of as needed per request") + parser.add_argument("--debug", action="store_true", help="Enable debug logging.") parser.add_argument("--disable-mirrored-groups", action="store_false", dest="enable_mirrored_groups", default=True, help="Disable creation of mirrored groups.") - parser.add_argument("--debug", action="store_true", help="Enable debug logging.") + parser.add_argument("--refresh-interval", type=int, default=60, + help="How often to refresh the database in seconds") + # Options for Microsoft Entra backend entra_group = parser.add_argument_group("Microsoft Entra") entra_group.add_argument("-t", "--entra-tenant-id", type=str, help="Microsoft Entra tenant ID.", required=False) @@ -32,6 +37,11 @@ redis_group = parser.add_argument_group("Redis") redis_group.add_argument("--redis-host", type=str, help="Host for Redis server.") redis_group.add_argument("--redis-port", type=int, help="Port for Redis server.") + # Options for TLS + tls_group = parser.add_argument_group("TLS") + tls_group.add_argument("--tls-certificate", type=str, help="Location of TLS certificate (pem).") + tls_group.add_argument("--tls-port", type=int, default=1636, help="Port to run on with encryption.") + tls_group.add_argument("--tls-private-key", type=str, help="Location of TLS private key (pem).") # Parse arguments args = parser.parse_args() From 6fb3ab920c448421af16eb6eda89cb6d5e7b5899 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Thu, 30 May 2024 13:22:53 +0100 Subject: [PATCH 2/8] :memo: Updated README and docstrings --- README.md | 6 +++--- apricot/ldap/oauth_ldap_server_factory.py | 6 +++++- apricot/ldap/oauth_ldap_tree.py | 2 ++ apricot/oauth/oauth_data_adaptor.py | 7 +++++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 348dc60..8911e95 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ To do this, you will need to provide the `--redis-host` and `--redis-port` argum ### Configure background refresh [Optional] -By default Apricot will refresh on demand when the data is older than 60 seconds. -If it takes a long time to fetch all users and groups, or you want to ensure that each request prompty gets a respose, you may want to configure background refresh to have it periodically be refreshed in the background. +By default Apricot will refresh the LDAP tree whenever it is accessed and it contains data older than 60 seconds. +If it takes a long time to fetch all users and groups, or you want to ensure that each request gets a prompt response, you may want to configure background refresh to have it periodically be refreshed in the background. -This is enabled with the `--background-refresh` flag, which uses the `--refresh-interval=60` parameter as the interval to refresh the ldap database. +This is enabled with the `--background-refresh` flag, which uses the `--refresh-interval` parameter as the interval to refresh the ldap database. ### Using TLS [Optional] diff --git a/apricot/ldap/oauth_ldap_server_factory.py b/apricot/ldap/oauth_ldap_server_factory.py index 3a61578..9b5f3d9 100644 --- a/apricot/ldap/oauth_ldap_server_factory.py +++ b/apricot/ldap/oauth_ldap_server_factory.py @@ -12,9 +12,13 @@ def __init__( self, domain: str, oauth_client: OAuthClient, *, background_refresh: bool, enable_mirrored_groups: bool, refresh_interval: int, ): """ - Initialise an LDAPServerFactory + Initialise an OAuthLDAPServerFactory + @param background_refresh: Whether to refresh the LDAP tree in the background rather than on access + @param domain: The root domain of the LDAP tree + @param enable_mirrored_groups: Create a mirrored LDAP group-of-groups for each group-of-users @param oauth_client: An OAuth client used to construct the LDAP tree + @param refresh_interval: Interval in seconds after which the tree must be refreshed """ # Create an LDAP lookup tree self.adaptor = OAuthLDAPTree( diff --git a/apricot/ldap/oauth_ldap_tree.py b/apricot/ldap/oauth_ldap_tree.py index 9d11023..41ed689 100644 --- a/apricot/ldap/oauth_ldap_tree.py +++ b/apricot/ldap/oauth_ldap_tree.py @@ -25,7 +25,9 @@ def __init__( """ Initialise an OAuthLDAPTree + @param background_refresh: Whether to refresh the LDAP tree in the background rather than on access @param domain: The root domain of the LDAP tree + @param enable_mirrored_groups: Create a mirrored LDAP group-of-groups for each group-of-users @param oauth_client: An OAuth client used to construct the LDAP tree @param refresh_interval: Interval in seconds after which the tree must be refreshed """ diff --git a/apricot/oauth/oauth_data_adaptor.py b/apricot/oauth/oauth_data_adaptor.py index e2e6ea5..58aaf8d 100644 --- a/apricot/oauth/oauth_data_adaptor.py +++ b/apricot/oauth/oauth_data_adaptor.py @@ -24,6 +24,13 @@ class OAuthDataAdaptor: def __init__( self, domain: str, oauth_client: OAuthClient, *, enable_mirrored_groups: bool ): + """ + Initialise an OAuthDataAdaptor + + @param domain: The root domain of the LDAP tree + @param enable_mirrored_groups: Create a mirrored LDAP group-of-groups for each group-of-users + @param oauth_client: An OAuth client used to construct the LDAP tree + """ self.debug = oauth_client.debug self.oauth_client = oauth_client self.root_dn = "DC=" + domain.replace(".", ",DC=") From 7c4f4b06ff8d137c122c3aefd22bc82917e23eda Mon Sep 17 00:00:00 2001 From: James Robinson Date: Thu, 30 May 2024 13:23:11 +0100 Subject: [PATCH 3/8] :rotating_light: Fix linting issues --- apricot/apricot_server.py | 27 +++++++++++++++++------ apricot/ldap/oauth_ldap_server_factory.py | 14 ++++++++++-- apricot/ldap/oauth_ldap_tree.py | 13 +++++++---- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/apricot/apricot_server.py b/apricot/apricot_server.py index 6d9f015..637c0fe 100644 --- a/apricot/apricot_server.py +++ b/apricot/apricot_server.py @@ -1,9 +1,9 @@ import inspect import sys -from typing import Any, cast, Optional +from typing import Any, Optional, cast from twisted.internet import reactor, task -from twisted.internet.endpoints import serverFromString, quoteStringArgument +from twisted.internet.endpoints import quoteStringArgument, serverFromString from twisted.internet.interfaces import IReactorCore, IStreamServerEndpoint from twisted.python import log @@ -71,12 +71,18 @@ def __init__( if self.debug: log.msg("Creating an LDAPServerFactory.") factory = OAuthLDAPServerFactory( - domain, oauth_client, background_refresh=background_refresh, enable_mirrored_groups=enable_mirrored_groups, refresh_interval=refresh_interval + domain, + oauth_client, + background_refresh=background_refresh, + enable_mirrored_groups=enable_mirrored_groups, + refresh_interval=refresh_interval, ) if background_refresh: if self.debug: - log.msg(f"Starting background refresh (interval={factory.adaptor.refresh_interval})") + log.msg( + f"Starting background refresh (interval={factory.adaptor.refresh_interval})" + ) loop = task.LoopingCall(factory.adaptor.refresh) loop.start(factory.adaptor.refresh_interval) @@ -88,11 +94,18 @@ def __init__( # Attach a listening endpoint if tls_port: - if not (tls_certificate or tls_private_key): - raise ValueError("No TLS certificate or private key provided. Make sure you provide these parameters or disable TLS by not providing the TLS port") + if not tls_certificate: + msg = "No TLS certificate provided. Please provide one with --tls-certificate or disable TLS by not providing the --tls-port argument." + raise ValueError(msg) + if not tls_private_key: + msg = "No TLS private key provided. Please provide one with --tls-private-key or disable TLS by not providing the --tls-port argument." + raise ValueError(msg) if self.debug: log.msg("Attaching a listening endpoint (TLS).") - ssl_endpoint: IStreamServerEndpoint = serverFromString(reactor, f"ssl:{tls_port}:privateKey={quoteStringArgument(tls_private_key)}:certKey={quoteStringArgument(tls_certificate)}") + ssl_endpoint: IStreamServerEndpoint = serverFromString( + reactor, + f"ssl:{tls_port}:privateKey={quoteStringArgument(tls_private_key)}:certKey={quoteStringArgument(tls_certificate)}", + ) ssl_endpoint.listen(factory) # Load the Twisted reactor diff --git a/apricot/ldap/oauth_ldap_server_factory.py b/apricot/ldap/oauth_ldap_server_factory.py index 9b5f3d9..303d9e4 100644 --- a/apricot/ldap/oauth_ldap_server_factory.py +++ b/apricot/ldap/oauth_ldap_server_factory.py @@ -9,7 +9,13 @@ class OAuthLDAPServerFactory(ServerFactory): def __init__( - self, domain: str, oauth_client: OAuthClient, *, background_refresh: bool, enable_mirrored_groups: bool, refresh_interval: int, + self, + domain: str, + oauth_client: OAuthClient, + *, + background_refresh: bool, + enable_mirrored_groups: bool, + refresh_interval: int, ): """ Initialise an OAuthLDAPServerFactory @@ -22,7 +28,11 @@ def __init__( """ # Create an LDAP lookup tree self.adaptor = OAuthLDAPTree( - domain, oauth_client, background_refresh=background_refresh, enable_mirrored_groups=enable_mirrored_groups, refresh_interval=refresh_interval + domain, + oauth_client, + background_refresh=background_refresh, + enable_mirrored_groups=enable_mirrored_groups, + refresh_interval=refresh_interval, ) def __repr__(self) -> str: diff --git a/apricot/ldap/oauth_ldap_tree.py b/apricot/ldap/oauth_ldap_tree.py index 41ed689..66e649f 100644 --- a/apricot/ldap/oauth_ldap_tree.py +++ b/apricot/ldap/oauth_ldap_tree.py @@ -20,7 +20,7 @@ def __init__( *, background_refresh: bool, enable_mirrored_groups: bool, - refresh_interval, + refresh_interval: int, ) -> None: """ Initialise an OAuthLDAPTree @@ -50,15 +50,20 @@ def root(self) -> OAuthLDAPEntry: Lazy-load the LDAP tree on request @return: An OAuthLDAPEntry for the tree + + @raises: ValueError. """ if not self.background_refresh: self.refresh() + if not self.root_: + msg = "LDAP tree could not be loaded" + raise ValueError(msg) return self.root_ - def refresh(self): + def refresh(self) -> None: if ( - not self.root_ - or (time.monotonic() - self.last_update) > self.refresh_interval + not self.root_ + or (time.monotonic() - self.last_update) > self.refresh_interval ): # Update users and groups from the OAuth server log.msg("Retrieving OAuth data.") From ba12088cabff749be1bc85e11083abb4896ec2f5 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Thu, 30 May 2024 13:25:07 +0100 Subject: [PATCH 4/8] :bug: Fix Dockerfile EXPOSE syntax --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b8940f1..cd6db06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,8 @@ COPY ./run.py . RUN chmod ugo+x ./entrypoint.sh # Open appropriate ports -EXPOSE 1389, 1636 +EXPOSE 1389 +EXPOSE 1636 # Run the server ENTRYPOINT ["./entrypoint.sh"] From 945a6b40a5a4410b83a5486020d79192aa0af184 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Thu, 30 May 2024 13:28:22 +0100 Subject: [PATCH 5/8] :wrench: Reordered entrypoint arguments --- docker/entrypoint.sh | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index bd96ceb..4739a1f 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -23,6 +23,7 @@ if [ -z "${DOMAIN}" ]; then exit 1 fi + # Arguments with defaults if [ -z "${PORT}" ]; then PORT="1389" @@ -30,7 +31,6 @@ if [ -z "${PORT}" ]; then fi - # Optional arguments EXTRA_OPTS="" if [ -n "${DEBUG}" ]; then @@ -41,6 +41,24 @@ if [ -n "${DISABLE_MIRRORED_GROUPS}" ]; then EXTRA_OPTS="${EXTRA_OPTS} --disable-mirrored-groups" fi + +# Backend arguments: Entra +if [ -n "${ENTRA_TENANT_ID}" ]; then + EXTRA_OPTS="${EXTRA_OPTS} --entra-tenant-id $ENTRA_TENANT_ID" +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" + exit 1 + fi + EXTRA_OPTS="${EXTRA_OPTS} --keycloak-base-url $KEYCLOAK_BASE_URL --keycloak-realm $KEYCLOAK_REALM" +fi + + +# LDAP refresh arguments if [ -n "${BACKGROUND_REFRESH}" ]; then EXTRA_OPTS="${EXTRA_OPTS} --background-refresh" fi @@ -49,12 +67,8 @@ if [ -n "${REFRESH_INTERVAL}" ]; then EXTRA_OPTS="${EXTRA_OPTS} --refresh-interval $REFRESH_INTERVAL" fi -if [ -n "${ENTRA_TENANT_ID}" ]; then - EXTRA_OPTS="${EXTRA_OPTS} --entra-tenant-id $ENTRA_TENANT_ID" -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" @@ -67,6 +81,8 @@ if [ -n "${TLS_PORT}" ]; then EXTRA_OPTS="${EXTRA_OPTS} --tls-port $TLS_PORT --tls-certificate $TLS_CERTIFICATE --tls-private-key $TLS_PRIVATE_KEY" fi + +# Redis arguments if [ -n "${REDIS_HOST}" ]; then if [ -z "${REDIS_PORT}" ]; then REDIS_PORT="6379" @@ -75,13 +91,6 @@ if [ -n "${REDIS_HOST}" ]; then EXTRA_OPTS="${EXTRA_OPTS} --redis-host $REDIS_HOST --redis-port $REDIS_PORT" fi -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" - exit 1 - fi - EXTRA_OPTS="${EXTRA_OPTS} --keycloak-base-url $KEYCLOAK_BASE_URL --keycloak-realm $KEYCLOAK_REALM" -fi # Run the server hatch run python run.py \ From e87fbea8151494d304005e79062e4b239b4402a0 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Thu, 30 May 2024 13:43:44 +0100 Subject: [PATCH 6/8] :rotating_light: Prefer '| None' over 'Optional' --- apricot/apricot_server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apricot/apricot_server.py b/apricot/apricot_server.py index 637c0fe..92cd5e8 100644 --- a/apricot/apricot_server.py +++ b/apricot/apricot_server.py @@ -1,6 +1,6 @@ import inspect import sys -from typing import Any, Optional, cast +from typing import Any, cast from twisted.internet import reactor, task from twisted.internet.endpoints import quoteStringArgument, serverFromString @@ -27,9 +27,9 @@ def __init__( redis_host: str | None = None, redis_port: int | None = None, refresh_interval: int = 60, - tls_port: Optional[int] = None, - tls_certificate: Optional[str] = None, - tls_private_key: Optional[str] = None, + tls_port: int | None = None, + tls_certificate: str | None = None, + tls_private_key: str | None = None, **kwargs: Any, ) -> None: self.debug = debug From c5528a2e9a111171c55cfdfabd4646a2a5dbad79 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Thu, 30 May 2024 13:53:52 +0100 Subject: [PATCH 7/8] :rotating_light: Linted run.py --- run.py | 78 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/run.py b/run.py index 3fbe7a6..98f4831 100644 --- a/run.py +++ b/run.py @@ -11,37 +11,75 @@ description="Apricot is a proxy for delegating LDAP requests to an OpenID Connect backend.", ) # Common options needed for all backends - parser.add_argument("-b", "--backend", type=OAuthBackend, help="Which OAuth backend to use.") - parser.add_argument("-d", "--domain", type=str, help="Which domain users belong to.") + parser.add_argument( + "-b", "--backend", type=OAuthBackend, help="Which OAuth backend to use." + ) + parser.add_argument( + "-d", "--domain", type=str, help="Which domain users belong to." + ) parser.add_argument("-i", "--client-id", type=str, help="OAuth client ID.") - parser.add_argument("-p", "--port", type=int, default=1389, help="Port to run on.") - parser.add_argument("-s", "--client-secret", type=str, help="OAuth client secret.") - parser.add_argument("--background-refresh", action="store_true", default=False, - help="Refresh in the background instead of as needed per request") - parser.add_argument("--debug", action="store_true", help="Enable debug logging.") - parser.add_argument("--disable-mirrored-groups", action="store_false", - dest="enable_mirrored_groups", default=True, - help="Disable creation of mirrored groups.") - parser.add_argument("--refresh-interval", type=int, default=60, - help="How often to refresh the database in seconds") + parser.add_argument( + "-p", "--port", type=int, default=1389, help="Port to run on." + ) + parser.add_argument( + "-s", "--client-secret", type=str, help="OAuth client secret." + ) + parser.add_argument( + "--background-refresh", + action="store_true", + default=False, + help="Refresh in the background instead of as needed per request", + ) + parser.add_argument( + "--debug", action="store_true", help="Enable debug logging." + ) + parser.add_argument( + "--disable-mirrored-groups", + action="store_false", + default=True, + dest="enable_mirrored_groups", + help="Disable creation of mirrored groups.", + ) + parser.add_argument( + "--refresh-interval", + type=int, + default=60, + help="How often to refresh the database in seconds", + ) # Options for Microsoft Entra backend entra_group = parser.add_argument_group("Microsoft Entra") - entra_group.add_argument("-t", "--entra-tenant-id", type=str, help="Microsoft Entra tenant ID.", required=False) + entra_group.add_argument( + "--entra-tenant-id", type=str, help="Microsoft Entra tenant ID." + ) # Options for Keycloak backend keycloak_group = parser.add_argument_group("Keycloak") - keycloak_group.add_argument("--keycloak-base-url", type=str, help="Keycloak base URL.", required=False) - keycloak_group.add_argument("--keycloak-realm", type=str, help="Keycloak Realm.", required=False) + keycloak_group.add_argument( + "--keycloak-base-url", type=str, help="Keycloak base URL." + ) + keycloak_group.add_argument( + "--keycloak-realm", type=str, help="Keycloak Realm." + ) # Options for Redis cache redis_group = parser.add_argument_group("Redis") - redis_group.add_argument("--redis-host", type=str, help="Host for Redis server.") - redis_group.add_argument("--redis-port", type=int, help="Port for Redis server.") + redis_group.add_argument( + "--redis-host", type=str, help="Host for Redis server." + ) + redis_group.add_argument( + "--redis-port", type=int, help="Port for Redis server." + ) # Options for TLS tls_group = parser.add_argument_group("TLS") - tls_group.add_argument("--tls-certificate", type=str, help="Location of TLS certificate (pem).") - tls_group.add_argument("--tls-port", type=int, default=1636, help="Port to run on with encryption.") - tls_group.add_argument("--tls-private-key", type=str, help="Location of TLS private key (pem).") + tls_group.add_argument( + "--tls-certificate", type=str, help="Location of TLS certificate (pem)." + ) + tls_group.add_argument( + "--tls-port", type=int, default=1636, help="Port to run on with encryption." + ) + tls_group.add_argument( + "--tls-private-key", type=str, help="Location of TLS private key (pem)." + ) # Parse arguments args = parser.parse_args() From 9b9011d231877523950da5f834a93423b4cd3b82 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Thu, 30 May 2024 14:02:42 +0100 Subject: [PATCH 8/8] :bug: Attempt to set up TLS if tls_certificate or tls_private_key are provided --- apricot/apricot_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apricot/apricot_server.py b/apricot/apricot_server.py index 92cd5e8..8776335 100644 --- a/apricot/apricot_server.py +++ b/apricot/apricot_server.py @@ -93,12 +93,12 @@ def __init__( endpoint.listen(factory) # Attach a listening endpoint - if tls_port: + if tls_certificate or tls_private_key: if not tls_certificate: - msg = "No TLS certificate provided. Please provide one with --tls-certificate or disable TLS by not providing the --tls-port argument." + msg = "No TLS certificate provided. Please provide one with --tls-certificate or disable TLS." raise ValueError(msg) if not tls_private_key: - msg = "No TLS private key provided. Please provide one with --tls-private-key or disable TLS by not providing the --tls-port argument." + msg = "No TLS private key provided. Please provide one with --tls-private-key or disable TLS." raise ValueError(msg) if self.debug: log.msg("Attaching a listening endpoint (TLS).")