Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Apricot for authentication/identity #1772

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
de17e60
:sparkles: Initial skeleton of SRE identity component
jemrobinson Feb 13, 2024
d4b4b6f
:sparkles: Add identity subnet and container group
jemrobinson Feb 13, 2024
6d3a6d1
:sparkles: Add an AzureADApplication to the identity component
jemrobinson Feb 14, 2024
f69cb84
:sparkles: Add a firewall rule for SRE identity servers
jemrobinson Feb 15, 2024
a2a790f
:wrench: Add network rules to allow workspaces to connect to SRE iden…
jemrobinson Feb 21, 2024
0d4421e
:recycle: Simplify 'security_group_name' to 'group_name'
jemrobinson Feb 26, 2024
5525095
:wrench: Add network rules to allow Guacamole desktop to connect to S…
jemrobinson Mar 27, 2024
4c189b2
:truck: Move ordering of SRE component construction
jemrobinson Apr 8, 2024
d217036
:arrow_up: Update to apricot 0.0.5 which allows user/group ID caching
jemrobinson Apr 8, 2024
29ca579
:sparkles: Set LDAP lookup variables and filters appropriate for Apri…
jemrobinson Apr 8, 2024
247edf5
:arrow_up: Update to guacamole-user-sync 0.4.0 which allows anonymous…
jemrobinson Apr 8, 2024
06fd05d
:arrow_up: Update workspace image to use SRE identity server instead …
jemrobinson Apr 8, 2024
c80a4cf
:coffin: Drop Domain SID extraction as this is not needed with Apricot
jemrobinson Apr 8, 2024
052a397
:recycle: Update Gitea server to use Apricot for identity
jemrobinson Apr 8, 2024
bea4172
:alien: Update connection details for Gitea database for new Azure Po…
jemrobinson Apr 8, 2024
6a1e9e9
:wrench: Allow network connections between user services containers a…
jemrobinson Apr 8, 2024
416f748
:recycle: Update HedgeDoc server to use Apricot for identity
jemrobinson Apr 8, 2024
aea776c
:wrench: Remove unused references to LDAP bind user/password in the SRE
jemrobinson Apr 8, 2024
85eede2
:coffin: Drop references to SHM identity subnet
jemrobinson Apr 8, 2024
1f0548e
:coffin: Create AzureAD groups in AzureAD rather than on the SHM DC
jemrobinson Apr 8, 2024
6b4a8b3
:recycle: Switch to AzureAD as default place to add/remove/alter user…
jemrobinson Apr 8, 2024
0fe059b
:coffin: Drop code for interacting with AzureAD on the DC
jemrobinson Apr 8, 2024
44770d0
:coffin: Drop password-domain-ldap-searcher from SHM
jemrobinson Apr 8, 2024
c84ccf2
:coffin: Remove remaining SRE dependencies on SHM domain controller r…
jemrobinson Apr 9, 2024
a6b53e9
:coffin: Drop unused identity support subnet
jemrobinson Apr 11, 2024
5bf4bf2
:bug: Construct SRE ldap_root_dn from SHM FQDN
jemrobinson Apr 11, 2024
050039c
Ensure NSG rule priorities are unique
JimMadge Apr 16, 2024
6c204e9
:recycle: Simplify alphanumeric function and use in GraphAPI
jemrobinson Apr 16, 2024
ef7eda2
:sparkles: Use ldap-filter to construct more readable LDAP filters
jemrobinson Apr 16, 2024
ca5ba6c
:sparkles: Add firewall priority enum
jemrobinson Apr 16, 2024
8646b65
:coffin: Remove unused port 389 for SRE identity servers
jemrobinson Apr 16, 2024
54d8591
Revert some changes of ef7eda247
JimMadge Apr 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 0 additions & 165 deletions data_safe_haven/administration/users/active_directory_users.py

This file was deleted.

4 changes: 0 additions & 4 deletions data_safe_haven/administration/users/azure_ad_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@ def list(self) -> Sequence[ResearchUser]:
user_principal_name=user_details["userPrincipalName"],
)
for user_details in user_list
if (
user_details["onPremisesSamAccountName"]
or user_details["isGlobalAdmin"]
)
]

def register(self, sre_name: str, usernames: Sequence[str]) -> None:
Expand Down
44 changes: 21 additions & 23 deletions data_safe_haven/administration/users/user_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from data_safe_haven.external import GraphApi
from data_safe_haven.utility import LoggingSingleton

from .active_directory_users import ActiveDirectoryUsers
from .azure_ad_users import AzureADUsers
from .guacamole_users import GuacamoleUsers
from .research_user import ResearchUser
Expand All @@ -19,7 +18,6 @@ def __init__(
config: Config,
graph_api: GraphApi,
):
self.active_directory_users = ActiveDirectoryUsers(config)
self.azure_ad_users = AzureADUsers(graph_api)
self.config = config
self.logger = LoggingSingleton()
Expand All @@ -34,7 +32,9 @@ def add(self, users_csv_path: pathlib.Path) -> None:
try:
# Construct user list
with open(users_csv_path, encoding="utf-8") as f_csv:
reader = csv.DictReader(f_csv)
dialect = csv.Sniffer().sniff(f_csv.read(), delimiters=";,")
f_csv.seek(0)
reader = csv.DictReader(f_csv, dialect=dialect)
for required_field in [
"GivenName",
"Surname",
Expand All @@ -49,6 +49,7 @@ def add(self, users_csv_path: pathlib.Path) -> None:
raise ValueError(msg)
users = [
ResearchUser(
account_enabled=True,
country=user["CountryCode"],
email_address=user["Email"],
given_name=user["GivenName"],
Expand All @@ -60,8 +61,8 @@ def add(self, users_csv_path: pathlib.Path) -> None:
for user in users:
self.logger.debug(f"Processing new user: {user}")

# Commit changes
self.active_directory_users.add(users)
# Add users to AzureAD
self.azure_ad_users.add(users)
except Exception as exc:
msg = f"Could not add users from '{users_csv_path}'.\n{exc}"
raise DataSafeHavenUserHandlingError(msg) from exc
Expand All @@ -70,7 +71,6 @@ def get_usernames(self) -> dict[str, list[str]]:
"""Load usernames from all sources"""
usernames = {}
usernames["Azure AD"] = self.get_usernames_azure_ad()
usernames["Domain controller"] = self.get_usernames_domain_controller()
for sre_name in self.config.sre_names:
usernames[f"SRE {sre_name}"] = self.get_usernames_guacamole(sre_name)
return usernames
Expand All @@ -79,10 +79,6 @@ def get_usernames_azure_ad(self) -> list[str]:
"""Load usernames from Azure AD"""
return [user.username for user in self.azure_ad_users.list()]

def get_usernames_domain_controller(self) -> list[str]:
"""Load usernames from all domain controller"""
return [user.username for user in self.active_directory_users.list()]

def get_usernames_guacamole(self, sre_name: str) -> list[str]:
"""Lazy-load usernames from Guacamole"""
try:
Expand Down Expand Up @@ -134,7 +130,7 @@ def register(self, sre_name: str, user_names: Sequence[str]) -> None:
"""
try:
# Add users to the SRE security group
self.active_directory_users.register(sre_name, user_names)
self.azure_ad_users.register(sre_name, user_names)
except Exception as exc:
msg = f"Could not register {len(user_names)} users with SRE '{sre_name}'.\n{exc}"
raise DataSafeHavenUserHandlingError(msg) from exc
Expand All @@ -147,14 +143,18 @@ def remove(self, user_names: Sequence[str]) -> None:
"""
try:
# Construct user lists
active_directory_users_to_remove = [
self.logger.info(f"Attempting to remove {len(user_names)} user(s).")
azuread_users_to_remove = [
user
for user in self.active_directory_users.list()
for user in self.azure_ad_users.list()
if user.username in user_names
]

# Commit changes
self.active_directory_users.remove(active_directory_users_to_remove)
self.logger.info(
f"Found {len(azuread_users_to_remove)} valid user(s) to remove."
)
self.azure_ad_users.remove(azuread_users_to_remove)
except Exception as exc:
msg = f"Could not remove users: {user_names}.\n{exc}"
raise DataSafeHavenUserHandlingError(msg) from exc
Expand Down Expand Up @@ -189,21 +189,19 @@ def set(self, users_csv_path: str) -> None:
self.logger.debug(f"Processing user: {user}")

# Keep existing users with the same username
active_directory_desired_users = [
azuread_desired_users = [
user
for user in self.active_directory_users.list()
for user in self.azure_ad_users.list()
if user.username in [u.username for u in desired_users]
]

# Construct list of new users
active_directory_desired_users = [
user
for user in desired_users
if user not in active_directory_desired_users
azuread_desired_users = [
user for user in desired_users if user not in azuread_desired_users
]

# Commit changes
self.active_directory_users.set(active_directory_desired_users)
self.azure_ad_users.set(azuread_desired_users)
except Exception as exc:
msg = f"Could not set users from '{users_csv_path}'.\n{exc}"
raise DataSafeHavenUserHandlingError(msg) from exc
Expand All @@ -216,7 +214,7 @@ def unregister(self, sre_name: str, user_names: Sequence[str]) -> None:
"""
try:
# Remove users from the SRE security group
self.active_directory_users.unregister(sre_name, user_names)
self.azure_ad_users.unregister(sre_name, user_names)
except Exception as exc:
msg = f"Could not register {len(user_names)} users with SRE '{sre_name}'.\n{exc}"
msg = f"Could not unregister {len(user_names)} users with SRE '{sre_name}'.\n{exc}"
raise DataSafeHavenUserHandlingError(msg) from exc
9 changes: 6 additions & 3 deletions data_safe_haven/commands/admin_add_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ def admin_add_users(csv_path: pathlib.Path) -> None:
shm_name = context.shm_name

try:
# Load GraphAPI as this may require user-interaction that is not
# possible as part of a Pulumi declarative command
# Load GraphAPI
graph_api = GraphApi(
tenant_id=config.shm.aad_tenant_id,
default_scopes=["Group.Read.All"],
default_scopes=[
"Group.Read.All",
"User.ReadWrite.All",
"UserAuthenticationMethod.ReadWrite.All",
],
)

# Add users to SHM
Expand Down
3 changes: 1 addition & 2 deletions data_safe_haven/commands/admin_list_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ def admin_list_users() -> None:
shm_name = context.shm_name

try:
# Load GraphAPI as this may require user-interaction that is not
# possible as part of a Pulumi declarative command
# Load GraphAPI
graph_api = GraphApi(
tenant_id=config.shm.aad_tenant_id,
default_scopes=["Directory.Read.All", "Group.Read.All"],
Expand Down
7 changes: 3 additions & 4 deletions data_safe_haven/commands/admin_register_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,15 @@ def admin_register_users(
f"Preparing to register {len(usernames)} user(s) with SRE '{sre_name}'"
)

# Load GraphAPI as this may require user-interaction that is not
# possible as part of a Pulumi declarative command
# Load GraphAPI
graph_api = GraphApi(
tenant_id=config.shm.aad_tenant_id,
default_scopes=["Group.Read.All"],
default_scopes=["Group.ReadWrite.All", "GroupMember.ReadWrite.All"],
)

# List users
users = UserHandler(config, graph_api)
available_usernames = users.get_usernames_domain_controller()
available_usernames = users.get_usernames_azure_ad()
usernames_to_register = []
for username in usernames:
if username in available_usernames:
Expand Down
5 changes: 2 additions & 3 deletions data_safe_haven/commands/admin_remove_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ def admin_remove_users(
shm_name = context.shm_name

try:
# Load GraphAPI as this may require user-interaction that is not
# possible as part of a Pulumi declarative command
# Load GraphAPI
graph_api = GraphApi(
tenant_id=config.shm.aad_tenant_id,
default_scopes=["Group.Read.All"],
default_scopes=["User.ReadWrite.All"],
)

# Remove users from SHM
Expand Down
Loading