diff --git a/finalize_manifest.py b/finalize_manifest.py index 3c023021..aa9a8393 100755 --- a/finalize_manifest.py +++ b/finalize_manifest.py @@ -35,11 +35,15 @@ def generate_trusted_files(root_dir, already_added_files): r'boot/.*' r'|\.dockerenv' r'|\.dockerinit' - r'|etc/mtab' r'|dev/.*' + r'|etc/gshadow.*' + r'|etc/mtab' + r'|etc/\.pwd\.lock' r'|etc/rc(\d|.)\.d/.*' + r'|etc/security/.*' + r'|etc/shadow.*' r'|gramine/python/.*' - r'|finalize_manifest\.py' + r'|gramine/app_files/finalize_manifest\.py' r'|proc/.*' r'|sys/.*' r'|var/.*)$') @@ -82,7 +86,7 @@ def generate_trusted_files(root_dir, already_added_files): def generate_library_paths(): encoding = sys.stdout.encoding if sys.stdout.encoding is not None else 'UTF-8' - ld_paths = subprocess.check_output('ldconfig -v', stderr=subprocess.PIPE, shell=True) + ld_paths = subprocess.check_output('ldconfig -v -N -X', stderr=subprocess.PIPE, shell=True) ld_paths = ld_paths.decode(encoding).splitlines() # Library paths start without whitespace (in contrast to libraries found under this path) @@ -107,7 +111,7 @@ def main(args=None): env = jinja2.Environment(loader=jinja2.FileSystemLoader('/')) env.globals.update({'library_paths': generate_library_paths(), 'env_path': os.getenv('PATH')}) - manifest = '/entrypoint.manifest' + manifest = '/gramine/app_files/entrypoint.manifest' rendered_manifest = env.get_template(manifest).render() rendered_manifest_dict = toml.loads(rendered_manifest) already_added_files = extract_files_from_user_manifest(rendered_manifest_dict) diff --git a/gsc.py b/gsc.py index 4d51ce34..c48a9760 100755 --- a/gsc.py +++ b/gsc.py @@ -124,6 +124,12 @@ def extract_build_args(args): sys.exit(1) return buildargs_dict +def extract_user_from_image_config(config, env): + user = config['User'] + if not user: + user = 'root' + env.globals.update({'app_user': user}) + def merge_two_dicts(dict1, dict2, path=[]): for key in dict2: if key in dict1: @@ -167,6 +173,7 @@ def gsc_build(args): env.globals.update(yaml.safe_load(args.config_file)) env.globals.update(vars(args)) env.globals.update({'app_image': original_image_name}) + extract_user_from_image_config(original_image.attrs['Config'], env) extract_binary_cmd_from_image_config(original_image.attrs['Config'], env) extract_working_dir_from_image_config(original_image.attrs['Config'], env) @@ -383,11 +390,14 @@ def gsc_info_image(args): # Create temporary directory on the host for sigstruct file with tempfile.TemporaryDirectory() as tmpdirname: + # Grant owner, group and everyone else read-write-execute permissions on temporary dir, so + # that even non-root users in Docker images can copy `entrypoint.sig` into it + os.chmod(tmpdirname,0o777) # Copy sigstruct file from Docker container into temporary directory on the host docker_socket.containers.run(args.image, - '\'cp entrypoint.sig /tmp/host/ 2>/dev/null || :\'', - entrypoint=['sh', '-c'], remove=True, - volumes={tmpdirname: {'bind': '/tmp/host', 'mode': 'rw'}}) + '\'cp /gramine/app_files/entrypoint.sig /tmp/host/ 2>/dev/null || :\'', + entrypoint=['sh', '-c'], remove=True, + volumes={tmpdirname: {'bind': '/tmp/host', 'mode': 'rw'}}) sigstruct = {} with open(os.path.join(tmpdirname, "entrypoint.sig"), 'rb') as sig: attr = read_sigstruct(sig.read()) diff --git a/templates/Dockerfile.common.build.template b/templates/Dockerfile.common.build.template index 54e4b5c0..14647c5c 100644 --- a/templates/Dockerfile.common.build.template +++ b/templates/Dockerfile.common.build.template @@ -8,9 +8,21 @@ FROM gsc-{{Gramine.Image}} AS gramine # Combine Gramine image with the original app image FROM {{app_image}} +# Temporarily switch to the root user to install packages +USER root + # Install distro-specific packages to run Gramine (e.g., python3, protobuf, toml, etc.) {% block install %}{% endblock %} +# Create a directory that will store apploader.sh and entrypoint files. +RUN mkdir -p /gramine/app_files + +# Make the app image user owner of /gramine/app_files directory +RUN chown {{app_user}} /gramine/app_files/ + +# Switch back to original app_image user +USER {{app_user}} + # Copy path-specific installation of Gramine {% if debug %} COPY --from=gramine /gramine/ /gramine/ @@ -19,28 +31,28 @@ COPY --from=gramine /gramine/meson_build_output /gramine/meson_build_output {% endif %} # Copy helper scripts and Gramine manifest -COPY *.py / -COPY apploader.sh / -COPY entrypoint.manifest / +COPY --chown={{app_user}} *.py /gramine/app_files/ +COPY --chown={{app_user}} apploader.sh /gramine/app_files/ +COPY --chown={{app_user}} entrypoint.manifest /gramine/app_files/ # Generate trusted arguments if required {% if not insecure_args %} RUN /gramine/meson_build_output/bin/gramine-argv-serializer \ - {{binary}} {{binary_arguments}} "{{"\" \"".join(cmd)}}" > /trusted_argv + {{binary}} {{binary_arguments}} "{{"\" \"".join(cmd)}}" > /gramine/app_files/trusted_argv {% endif %} # Docker entrypoint/cmd typically contains only the basename of the executable so create a symlink -RUN cd / \ +RUN cd /gramine/app_files/ \ && which {{binary}} | xargs ln -s || true # Include Meson build output directory in $PATH ENV PATH="/gramine/meson_build_output/bin:$PATH" # Mark apploader.sh executable, finalize manifest, and remove intermediate scripts -RUN chmod u+x /apploader.sh \ - && /usr/bin/python3 -B /finalize_manifest.py \ - && rm -f /finalize_manifest.py +RUN chmod u+x /gramine/app_files/apploader.sh \ + && /usr/bin/python3 -B /gramine/app_files/finalize_manifest.py \ + && rm -f /gramine/app_files/finalize_manifest.py # Define default command -ENTRYPOINT ["/bin/bash", "/apploader.sh"] +ENTRYPOINT ["/bin/bash", "/gramine/app_files/apploader.sh"] CMD [{% if insecure_args %} "{{'", "'.join(cmd)}}" {% endif %}] diff --git a/templates/Dockerfile.common.sign.template b/templates/Dockerfile.common.sign.template index 606ecef3..8040a35c 100644 --- a/templates/Dockerfile.common.sign.template +++ b/templates/Dockerfile.common.sign.template @@ -1,15 +1,15 @@ # Sign image in a separate stage to ensure that signing key is never part of the final image FROM {{image}} as unsigned_image -COPY gsc-signer-key.pem /gsc-signer-key.pem +COPY gsc-signer-key.pem /gramine/app_files/gsc-signer-key.pem RUN {% block path %}{% endblock %} gramine-sgx-sign \ - --key /gsc-signer-key.pem \ - --manifest /entrypoint.manifest \ - --output /entrypoint.manifest.sgx + --key /gramine/app_files/gsc-signer-key.pem \ + --manifest /gramine/app_files/entrypoint.manifest \ + --output /gramine/app_files/entrypoint.manifest.sgx # This trick removes all temporary files from the previous commands (including gsc-signer-key.pem) FROM {{image}} -COPY --from=unsigned_image /*.sig / -COPY --from=unsigned_image /*.sgx / +COPY --from=unsigned_image /gramine/app_files/*.sig /gramine/app_files/ +COPY --from=unsigned_image /gramine/app_files/*.sgx /gramine/app_files/ diff --git a/templates/apploader.common.template b/templates/apploader.common.template index ccc0e410..48c852a4 100644 --- a/templates/apploader.common.template +++ b/templates/apploader.common.template @@ -11,8 +11,10 @@ set -e # Default to Linux-SGX if no PAL was specified if [ -z "$GSC_PAL" ] || [ "$GSC_PAL" == "Linux-SGX" ] then - gramine-sgx-get-token --quiet --sig /entrypoint.sig --output /entrypoint.token - gramine-sgx /entrypoint {% if insecure_args %}{{binary_arguments}} "${@}"{% endif %} + gramine-sgx-get-token --quiet \ + --sig /gramine/app_files/entrypoint.sig --output /gramine/app_files/entrypoint.token + gramine-sgx /gramine/app_files/entrypoint \ + {% if insecure_args %}{{binary_arguments}} "${@}"{% endif %} else - gramine-direct /entrypoint {{binary_arguments}} "${@}" + gramine-direct /gramine/app_files/entrypoint {{binary_arguments}} "${@}" fi diff --git a/templates/entrypoint.common.manifest.template b/templates/entrypoint.common.manifest.template index 20f2902e..5fbc8c37 100644 --- a/templates/entrypoint.common.manifest.template +++ b/templates/entrypoint.common.manifest.template @@ -1,4 +1,4 @@ -libos.entrypoint = "/{{binary}}" +libos.entrypoint = "/gramine/app_files/{{binary}}" # Add distro-specific `loader.entrypoint` and `loader.env.LD_LIBRARY_PATH` {% block loader %}{% endblock %} @@ -22,9 +22,9 @@ sgx.debug = {% if debug %} true {% else %} false {% endif %} loader.argv0_override = "{{binary}}" loader.insecure__use_cmdline_argv = true {% else %} -loader.argv_src_file = "file:/trusted_argv" +loader.argv_src_file = "file:/gramine/app_files/trusted_argv" sgx.trusted_files = [ - "file:/trusted_argv", + "file:/gramine/app_files/trusted_argv", ] {% endif %} diff --git a/test/generic.manifest b/test/generic.manifest index 0700527c..71891da4 100644 --- a/test/generic.manifest +++ b/test/generic.manifest @@ -5,5 +5,5 @@ sgx.enclave_size = "4G" sgx.thread_num = 8 sgx.trusted_files = [ - "file:/entrypoint.manifest", # unused entry, only to test merging of manifests + "file:/gramine/app_files/entrypoint.manifest", # unused entry, only to test merging of manifests ]