diff --git a/.env-devel b/.env-devel index 9a62f1511fe..5247d1b179b 100644 --- a/.env-devel +++ b/.env-devel @@ -65,6 +65,10 @@ DIRECTOR_PORT=8080 DIRECTOR_REGISTRY_CACHING_TTL=900 DIRECTOR_REGISTRY_CACHING=True +EFS_USER_ID=8006 +EFS_USER_NAME=efs +EFS_GROUP_ID=8106 +EFS_GROUP_NAME=efs-group EFS_DNS_NAME=fs-xxx.efs.us-east-1.amazonaws.com EFS_MOUNTED_PATH=/tmp/efs EFS_PROJECT_SPECIFIC_DATA_DIRECTORY=project-specific-data @@ -169,6 +173,10 @@ R_CLONE_OPTION_RETRIES=3 R_CLONE_OPTION_TRANSFERS=5 R_CLONE_PROVIDER=MINIO +# simcore-user used in docker images +SC_USER_ID=8004 +SC_USER_NAME=scu + S3_ACCESS_KEY=12345678 S3_BUCKET_NAME=simcore S3_ENDPOINT=http://172.17.0.1:9001 diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 61197234759..d7facbde40e 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -365,6 +365,12 @@ services: RABBIT_PORT: ${RABBIT_PORT} RABBIT_SECURE: ${RABBIT_SECURE} RABBIT_USER: ${RABBIT_USER} + SC_USER_ID: ${SC_USER_ID} + SC_USER_NAME: ${SC_USER_NAME} + EFS_USER_ID: ${EFS_USER_ID} + EFS_USER_NAME: ${EFS_USER_NAME} + EFS_GROUP_ID: ${EFS_GROUP_ID} + EFS_GROUP_NAME: ${EFS_GROUP_NAME} EFS_DNS_NAME: ${EFS_DNS_NAME} EFS_MOUNTED_PATH: ${EFS_MOUNTED_PATH} EFS_ONLY_ENABLED_FOR_USERIDS: ${EFS_ONLY_ENABLED_FOR_USERIDS} diff --git a/services/efs-guardian/Dockerfile b/services/efs-guardian/Dockerfile index 129abe9457a..a6e187aace4 100644 --- a/services/efs-guardian/Dockerfile +++ b/services/efs-guardian/Dockerfile @@ -45,26 +45,44 @@ RUN --mount=type=cache,target=/var/cache/apt,mode=0755,sharing=private \ && gosu nobody true # simcore-user uid=8004(scu) gid=8004(scu) groups=8004(scu) +# simcore-efs-user uid=8006(efs) gid=8006(efs) groups=8006(efs) +# Currently all simcore services run as user 8004. The Guardian needs to run as a different user 8006. +# The Guardian has access to the root directory of the EFS distributed file system +# and can change the file permissions of user 8004 if needed. ENV SC_USER_ID=8004 \ SC_USER_NAME=scu \ + EFS_USER_ID=8006 \ + EFS_USER_NAME=efs \ SC_BUILD_TARGET=base \ SC_BOOT_MODE=default RUN adduser \ - --uid ${SC_USER_ID} \ + --uid 8004 \ --disabled-password \ --gecos "" \ --shell /bin/sh \ - --home /home/${SC_USER_NAME} \ - ${SC_USER_NAME} + --home /home/scu \ + scu +RUN adduser \ + --uid 8006 \ + --disabled-password \ + --gecos "" \ + --shell /bin/sh \ + --home /home/efs \ + efs + +# we create efs-group which is then used in efs guardian when he is creating a directory for user services. +RUN groupadd -g 8106 efs-group +RUN usermod -a -G efs-group efs +RUN usermod -a -G efs-group scu # Sets utf-8 encoding for Python et al ENV LANG=C.UTF-8 # Turns off writing .pyc files; superfluous on an ephemeral container. ENV PYTHONDONTWRITEBYTECODE=1 \ - VIRTUAL_ENV=/home/scu/.venv + VIRTUAL_ENV=/home/efs/.venv # Ensures that the python and pip executables used in the image will be # those from our virtualenv. @@ -149,15 +167,15 @@ ENV SC_BUILD_TARGET=production \ ENV PYTHONOPTIMIZE=TRUE -WORKDIR /home/scu -# ensure home folder is read/writable for user scu -RUN chown -R scu /home/scu +WORKDIR /home/efs +# ensure home folder is read/writable for user efs +RUN chown -R efs /home/efs # Starting from clean base image, copies pre-installed virtualenv from prod-only-deps -COPY --chown=scu:scu --from=prod-only-deps ${VIRTUAL_ENV} ${VIRTUAL_ENV} +COPY --chown=efs:efs --from=prod-only-deps ${VIRTUAL_ENV} ${VIRTUAL_ENV} # Copies booting scripts -COPY --chown=scu:scu services/efs-guardian/docker services/efs-guardian/docker +COPY --chown=efs:efs services/efs-guardian/docker services/efs-guardian/docker RUN chmod +x services/efs-guardian/docker/*.sh @@ -187,7 +205,7 @@ ENV SC_BUILD_TARGET=development \ WORKDIR /devel -RUN chown -R scu:scu "${VIRTUAL_ENV}" +RUN chown -R efs:efs "${VIRTUAL_ENV}" ENTRYPOINT ["/bin/sh", "services/efs-guardian/docker/entrypoint.sh"] CMD ["/bin/sh", "services/efs-guardian/docker/boot.sh"] diff --git a/services/efs-guardian/docker/entrypoint.sh b/services/efs-guardian/docker/entrypoint.sh index ac4bcf76085..5e58e8e87c8 100755 --- a/services/efs-guardian/docker/entrypoint.sh +++ b/services/efs-guardian/docker/entrypoint.sh @@ -43,30 +43,30 @@ if [ "${SC_BUILD_TARGET}" = "development" ]; then HOST_GROUPID=$(stat --format=%g "${SC_DEVEL_MOUNT}") CONT_GROUPNAME=$(getent group "${HOST_GROUPID}" | cut --delimiter=: --fields=1) if [ "$HOST_USERID" -eq 0 ]; then - echo "$WARNING" "Folder mounted owned by root user... adding $SC_USER_NAME to root..." - adduser "$SC_USER_NAME" root + echo "$WARNING" "Folder mounted owned by root user... adding $EFS_USER_NAME to root..." + adduser "$EFS_USER_NAME" root else echo "$INFO" "Folder mounted owned by user $HOST_USERID:$HOST_GROUPID-'$CONT_GROUPNAME'..." - # take host's credentials in $SC_USER_NAME + # take host's credentials in $EFS_USER_NAME if [ -z "$CONT_GROUPNAME" ]; then - echo "$WARNING" "Creating new group grp$SC_USER_NAME" - CONT_GROUPNAME=grp$SC_USER_NAME + echo "$WARNING" "Creating new group grp$EFS_USER_NAME" + CONT_GROUPNAME=grp$EFS_USER_NAME addgroup --gid "$HOST_GROUPID" "$CONT_GROUPNAME" else echo "$INFO" "group already exists" fi - echo "$INFO" "Adding $SC_USER_NAME to group $CONT_GROUPNAME..." - adduser "$SC_USER_NAME" "$CONT_GROUPNAME" + echo "$INFO" "Adding $EFS_USER_NAME to group $CONT_GROUPNAME..." + adduser "$EFS_USER_NAME" "$CONT_GROUPNAME" echo "$WARNING" "Changing ownership [this could take some time]" - echo "$INFO" "Changing $SC_USER_NAME:$SC_USER_NAME ($SC_USER_ID:$SC_USER_ID) to $SC_USER_NAME:$CONT_GROUPNAME ($HOST_USERID:$HOST_GROUPID)" - usermod --uid "$HOST_USERID" --gid "$HOST_GROUPID" "$SC_USER_NAME" + echo "$INFO" "Changing $EFS_USER_NAME:$EFS_USER_NAME ($EFS_USER_ID:$EFS_USER_ID) to $EFS_USER_NAME:$CONT_GROUPNAME ($HOST_USERID:$HOST_GROUPID)" + usermod --uid "$HOST_USERID" --gid "$HOST_GROUPID" "$EFS_USER_NAME" - echo "$INFO" "Changing group properties of files around from $SC_USER_ID to group $CONT_GROUPNAME" - find / -path /proc -prune -o -group "$SC_USER_ID" -exec chgrp --no-dereference "$CONT_GROUPNAME" {} \; + echo "$INFO" "Changing group properties of files around from $EFS_USER_ID to group $CONT_GROUPNAME" + find / -path /proc -prune -o -group "$EFS_USER_ID" -exec chgrp --no-dereference "$CONT_GROUPNAME" {} \; # change user property of files already around - echo "$INFO" "Changing ownership properties of files around from $SC_USER_ID to group $CONT_GROUPNAME" - find / -path /proc -prune -o -user "$SC_USER_ID" -exec chown --no-dereference "$SC_USER_NAME" {} \; + echo "$INFO" "Changing ownership properties of files around from $EFS_USER_ID to group $CONT_GROUPNAME" + find / -path /proc -prune -o -user "$EFS_USER_ID" -exec chown --no-dereference "$EFS_USER_NAME" {} \; fi fi @@ -84,11 +84,11 @@ if stat $DOCKER_MOUNT >/dev/null 2>&1; then GROUPNAME=$(getent group "${GROUPID}" | cut --delimiter=: --fields=1) echo "$WARNING docker group with $GROUPID has name $GROUPNAME" fi - adduser "$SC_USER_NAME" "$GROUPNAME" + adduser "$EFS_USER_NAME" "$GROUPNAME" fi echo "$INFO Starting $* ..." -echo " $SC_USER_NAME rights : $(id "$SC_USER_NAME")" +echo " $EFS_USER_NAME rights : $(id "$EFS_USER_NAME")" echo " local dir : $(ls -al)" -exec gosu "$SC_USER_NAME" "$@" +exec gosu "$EFS_USER_NAME" "$@" diff --git a/services/efs-guardian/src/simcore_service_efs_guardian/core/settings.py b/services/efs-guardian/src/simcore_service_efs_guardian/core/settings.py index adf172a4b0c..92514a3d93a 100644 --- a/services/efs-guardian/src/simcore_service_efs_guardian/core/settings.py +++ b/services/efs-guardian/src/simcore_service_efs_guardian/core/settings.py @@ -41,8 +41,21 @@ class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings): "then the check is considered to have failed." "It takes retries consecutive failures of the health check for the container to be considered unhealthy.", ) - SC_USER_ID: int | None = None - SC_USER_NAME: str | None = None + SC_USER_ID: int + SC_USER_NAME: str + + EFS_USER_ID: int = Field( + description="Linux user ID that the Guardian service will run with" + ) + EFS_USER_NAME: str = Field( + description="Linux user name that the Guardian service will run with" + ) + EFS_GROUP_ID: int = Field( + description="Linux group ID that the EFS and Simcore linux users are part of" + ) + EFS_GROUP_NAME: str = Field( + description="Linux group name that the EFS and Simcore linux users are part of" + ) # RUNTIME ----------------------------------------------------------- EFS_GUARDIAN_DEBUG: bool = Field( diff --git a/services/efs-guardian/src/simcore_service_efs_guardian/services/efs_manager.py b/services/efs-guardian/src/simcore_service_efs_guardian/services/efs_manager.py index d3c2d0802b5..401f38ed1b5 100644 --- a/services/efs-guardian/src/simcore_service_efs_guardian/services/efs_manager.py +++ b/services/efs-guardian/src/simcore_service_efs_guardian/services/efs_manager.py @@ -1,3 +1,4 @@ +import os from dataclasses import dataclass from pathlib import Path @@ -5,6 +6,8 @@ from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID +from ..core.settings import ApplicationSettings, get_application_settings + @dataclass(frozen=True) class EfsManager: @@ -12,6 +15,7 @@ class EfsManager: _efs_mounted_path: Path _project_specific_data_base_directory: str + _settings: ApplicationSettings @classmethod async def create( @@ -20,7 +24,10 @@ async def create( efs_mounted_path: Path, project_specific_data_base_directory: str, ): - return cls(app, efs_mounted_path, project_specific_data_base_directory) + settings = get_application_settings(app) + return cls( + app, efs_mounted_path, project_specific_data_base_directory, settings + ) async def initialize_directories(self): _dir_path = self._efs_mounted_path / self._project_specific_data_base_directory @@ -36,5 +43,12 @@ async def create_project_specific_data_dir( / f"{node_id}" / f"{storage_directory_name}" ) + # Ensure the directory exists with the right parents Path.mkdir(_dir_path, parents=True, exist_ok=True) + # Change the owner to user id 8006(efs) and group id 8106(efs-group) + os.chown(_dir_path, self._settings.EFS_USER_ID, self._settings.EFS_GROUP_ID) + # Set directory permissions to allow group write access + Path.chmod( + _dir_path, 0o770 + ) # This gives rwx permissions to user and group, and nothing to others return _dir_path diff --git a/services/efs-guardian/tests/unit/test_efs_manager.py b/services/efs-guardian/tests/unit/test_efs_manager.py index cd5b9a755a3..42e22c9386d 100644 --- a/services/efs-guardian/tests/unit/test_efs_manager.py +++ b/services/efs-guardian/tests/unit/test_efs_manager.py @@ -5,6 +5,7 @@ # pylint: disable=unused-variable from pathlib import Path +from unittest.mock import patch import pytest from faker import Faker @@ -45,12 +46,17 @@ async def test_rpc_create_project_specific_data_dir( _node_id = faker.uuid4() _storage_directory_name = faker.name() - result = await efs_manager.create_project_specific_data_dir( - rpc_client, - project_id=_project_id, - node_id=_node_id, - storage_directory_name=_storage_directory_name, - ) + with patch( + "simcore_service_efs_guardian.services.efs_manager.os.chown" + ) as mocked_chown: + result = await efs_manager.create_project_specific_data_dir( + rpc_client, + project_id=_project_id, + node_id=_node_id, + storage_directory_name=_storage_directory_name, + ) + mocked_chown.assert_called_once() + assert isinstance(result, Path) _expected_path = ( aws_efs_settings.EFS_MOUNTED_PATH