diff --git a/.github/workflows/build-container-prep-image.yml b/.github/workflows/build-container-prep-image.yml index 2470adf..b6b6642 100644 --- a/.github/workflows/build-container-prep-image.yml +++ b/.github/workflows/build-container-prep-image.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - lfs: 'true' + lfs: "true" - name: Build image run: git lfs pull ; docker build . -f ./client/container_preparation/Dockerfile -t $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" @@ -33,10 +33,10 @@ jobs: # This strips the "v" prefix from the tag name. [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') - + # This uses the Docker `latest` tag convention. [ "$VERSION" == "main" ] && VERSION=latest echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker push $IMAGE_ID:$VERSION \ No newline at end of file + docker push $IMAGE_ID:$VERSION diff --git a/.github/workflows/build-data-prep-image.yml b/.github/workflows/build-data-prep-image.yml index 39ed48f..307ce16 100644 --- a/.github/workflows/build-data-prep-image.yml +++ b/.github/workflows/build-data-prep-image.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - lfs: 'true' + lfs: "true" - name: Build image run: git lfs pull ; docker build . -f ./client/data_preparation/Dockerfile -t $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" @@ -33,10 +33,10 @@ jobs: # This strips the "v" prefix from the tag name. [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') - + # This uses the Docker `latest` tag convention. [ "$VERSION" == "main" ] && VERSION=latest echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker push $IMAGE_ID:$VERSION \ No newline at end of file + docker push $IMAGE_ID:$VERSION diff --git a/.github/workflows/build-job-prep-image.yml b/.github/workflows/build-job-prep-image.yml index 992fd00..1e62799 100644 --- a/.github/workflows/build-job-prep-image.yml +++ b/.github/workflows/build-job-prep-image.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - lfs: 'true' + lfs: "true" - name: Build image run: git lfs pull ; docker build . -f ./client/job_preparation/Dockerfile -t $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" @@ -33,10 +33,10 @@ jobs: # This strips the "v" prefix from the tag name. [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') - + # This uses the Docker `latest` tag convention. [ "$VERSION" == "main" ] && VERSION=latest echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker push $IMAGE_ID:$VERSION \ No newline at end of file + docker push $IMAGE_ID:$VERSION diff --git a/.github/workflows/build-server-image.yml b/.github/workflows/build-server-image.yml index 09f701d..97657d9 100644 --- a/.github/workflows/build-server-image.yml +++ b/.github/workflows/build-server-image.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - lfs: 'true' + lfs: "true" - name: Build image run: git lfs pull ; docker build . -f ./server/Dockerfile -t $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" @@ -33,10 +33,10 @@ jobs: # This strips the "v" prefix from the tag name. [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') - + # This uses the Docker `latest` tag convention. [ "$VERSION" == "main" ] && VERSION=latest echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker push $IMAGE_ID:$VERSION \ No newline at end of file + docker push $IMAGE_ID:$VERSION diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99159d0..7c67da9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,35 +1,34 @@ repos: -# Base repo -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + # Base repo + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files -# Code formatting using Black (python) -- repo: https://github.com/psf/black - rev: 24.2.0 + # Code formatting using Black (python) + - repo: https://github.com/psf/black + rev: 24.3.0 hooks: - - id: black + - id: black -# Dockerfile lint -- repo: https://github.com/pryorda/dockerfilelint-precommit-hooks - rev: v0.1.0 - hooks: - - id: dockerfilelint - stages: [commit] + # Dockerfile lint + - repo: https://github.com/hadolint/hadolint + rev: v2.12.1-beta + hooks: + - id: hadolint -# Code formatting using beautysh (bash) -- repo: https://github.com/lovesegfault/beautysh - rev: v6.2.1 - hooks: - - id: beautysh + # Code formatting using beautysh (bash) + - repo: https://github.com/scop/pre-commit-shfmt + rev: v3.8.0-1 + hooks: + - id: shfmt -# Markdown lint -- repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.39.0 - hooks: - - id: markdownlint - \ No newline at end of file + # Markdown lint + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 + hooks: + - id: prettier + files: \.(js|ts|jsx|tsx|css|less|html|json|markdown|md|yaml|yml)$ diff --git a/LICENSE b/LICENSE index 0c4a2a0..405a8c7 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 743665f..c1c75ca 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ## Main goal This partnership project involving CSC and Hewlett Packard Enterprise aims to enable HPC users to run secured jobs. It provides tools to enable anyone running secured jobs with encrypted data and specific confidential containers on a supercomputing site, leveraging (non exhaustively) : + - [SPIFFE/SPIRE](https://github.com/spiffe/spire) - [Hashicorp Vault](https://github.com/hashicorp/vault) - [Singularity / Apptainer encryption](https://github.com/apptainer/apptainer) diff --git a/client/container_preparation/Dockerfile b/client/container_preparation/Dockerfile index bdcb9e2..1efac8c 100644 --- a/client/container_preparation/Dockerfile +++ b/client/container_preparation/Dockerfile @@ -1,39 +1,39 @@ # Using Python original Docker image -FROM --platform=linux/amd64 python:3.9-alpine - -# Install necessary packages -RUN apk add \ - git \ - curl \ - jq \ - build-base \ - libffi-dev - -RUN curl https://sh.rustup.rs -sSf -o rustup.sh ; chmod +x rustup.sh ; ./rustup.sh -y +ARG BUILDPLATFORM=linux/amd64 +FROM --platform=$BUILDPLATFORM python:3.9-alpine + +# Install necessary packages, spire-agent and rust +RUN apk add --no-cache \ + git=2.43.0-r0 \ + curl=8.5.0-r0 \ + jq=1.7.1-r0 \ + build-base=0.5-r3 \ + libffi-dev=3.4.4-r3 && \ +curl -LsSf -o spire-1.9.0-linux-amd64-musl.tar.gz https://github.com/spiffe/spire/releases/download/v1.9.0/spire-1.9.0-linux-amd64-musl.tar.gz && \ +tar xvf spire-1.9.0-linux-amd64-musl.tar.gz ; mv spire-1.9.0 /opt ; mv /opt/spire-1.9.0 /opt/spire && \ +ln -s /opt/spire/bin/spire-agent /usr/bin/spire-agent && \ +ln -s /opt/spire/bin/spire-server /usr/bin/spire-server && \ +rm -rf spire-1.9.0-linux-amd64-musl.tar.gz && \ +curl https://sh.rustup.rs -sSf -o rustup.sh ; chmod +x rustup.sh ; ./rustup.sh -y + +# Add rust binaries to PATH ENV PATH="$PATH:/root/.cargo/bin" -# Install spire-agent -RUN wget -q https://github.com/spiffe/spire/releases/download/v1.9.1/spire-1.9.1-linux-amd64-musl.tar.gz -RUN tar xvf spire-1.9.1-linux-amd64-musl.tar.gz ; mv spire-1.9.1 /opt ; mv /opt/spire-1.9.1 /opt/spire -RUN ln -s /opt/spire/bin/spire-agent /usr/bin/spire-agent - -# Install pyspiffe package -RUN pip install git+https://github.com/HewlettPackard/py-spiffe.git@3640af9d6629c05e027f99010abc934cb74122a8 - # Create code directory, output directory -RUN mkdir /container_preparation /output ; chmod -R 777 /output +RUN mkdir /container_preparation /output # Copy useful data from the project COPY ./client/container_preparation /container_preparation +# Set workdir +WORKDIR /container_preparation + # Install dependencies -RUN cd /container_preparation && pip install -r ./requirements.txt +RUN pip install --no-cache-dir -r ./requirements.txt && \ +pip install --no-cache-dir git+https://github.com/HewlettPackard/py-spiffe.git@3640af9d6629c05e027f99010abc934cb74122a8 # Copy utils for SPIFFEID creation ... COPY ./utils /container_preparation/utils -# Set workdir -WORKDIR /container_preparation - # Set entrypoint ENTRYPOINT [ "./entrypoint.sh" ] diff --git a/client/container_preparation/README.md b/client/container_preparation/README.md index e7e387e..0ed6ae0 100644 --- a/client/container_preparation/README.md +++ b/client/container_preparation/README.md @@ -1,18 +1,20 @@ # Introduction + This directory contains code which prepares existing OCI images to be used on LUMI in a secure way. The code adds layers to handle encryption and encrypts the resulting apptainer (singularity) image itself. ## Current state Currently, the container_preparation.py script is able to run most of the needed tasks + - Create a new receipe (Dockerfile) prepared for secure workloads - Build the new image - Build an apptainer image based on the just built one - - Unencrypted - - But unfortunately not encrypted for the moment - + - Unencrypted + - But unfortunately not encrypted for the moment What is missing : + - Encryption of the container - Crypt binary inside of the resulting container and the logic needed to encrypt ouput data before leaving the container - Documentation (global) - Explanation of how it works, what is needed ... diff --git a/client/container_preparation/entrypoint.sh b/client/container_preparation/entrypoint.sh index 5176529..3173f76 100755 --- a/client/container_preparation/entrypoint.sh +++ b/client/container_preparation/entrypoint.sh @@ -8,46 +8,88 @@ docker_path="/var/run/docker.sock" # Argument parser, arguments for both container preparation and key shipping should be handled here. parse_args() { - while [[ "$#" -gt 0 ]]; do - case "$1" in - --config) config="$2"; shift 2 ;; - -b|--base-oci-image) base_oci_image="$2"; shift 2 ;; - -s|--sif-path) sif_path="$2"; shift 2 ;; - -e|--encrypted) encrypted=true; shift ;; - --data-path) data_path="$2"; shift 2 ;; - --data-path-at-rest) data_path_at_rest="$2"; shift 2 ;; - --username) username="$2"; shift 2 ;; - -u|--users) users="$2" ; shift 2 ;; - -g|--groups) groups="$2" ; shift 2 ;; - -c|--compute-nodes) compute_nodes="$2" ; shift 2 ;; - -d|--docker-path) docker_path="$2" ; shift 2 ;; - -h|--help) python3 ./prepare_container.py --help ; python3 ./utils/ship_a_key.py --help ; exit 0 ;; - *) echo "Error: Unknown option $1"; python3 ./prepare_container.py --help ; python3 ./utils/ship_a_key.py --help ; exit 1 ;; - esac - done - - # Check for required arguments - if [ -z "$config" ] || [ -z "$base_oci_image" ] || [ -z "$sif_path" ] || [ -z "$data_path" ] || [ -z "$data_path_at_rest" ] || ( [ -z "$users" ] && [ -z "$groups" ] ) || [ -z "$compute_nodes" ]; then - echo echo "Please provides options for both of these programs : " - python3 ./prepare_container.py --help - python3 ./utils/ship_a_key.py --help - exit 1 - fi + while [[ "$#" -gt 0 ]]; do + case "$1" in + --config) + config="$2" + shift 2 + ;; + -b | --base-oci-image) + base_oci_image="$2" + shift 2 + ;; + -s | --sif-path) + sif_path="$2" + shift 2 + ;; + -e | --encrypted) + encrypted=true + shift + ;; + --data-path) + data_path="$2" + shift 2 + ;; + --data-path-at-rest) + data_path_at_rest="$2" + shift 2 + ;; + --username) + username="$2" + shift 2 + ;; + -u | --users) + users="$2" + shift 2 + ;; + -g | --groups) + groups="$2" + shift 2 + ;; + -c | --compute-nodes) + compute_nodes="$2" + shift 2 + ;; + -d | --docker-path) + docker_path="$2" + shift 2 + ;; + -h | --help) + python3 ./prepare_container.py --help + python3 ./utils/ship_a_key.py --help + exit 0 + ;; + *) + echo "Error: Unknown option $1" + python3 ./prepare_container.py --help + python3 ./utils/ship_a_key.py --help + exit 1 + ;; + esac + done + + # Check for required arguments + if [ -z "$config" ] || [ -z "$base_oci_image" ] || [ -z "$sif_path" ] || [ -z "$data_path" ] || [ -z "$data_path_at_rest" ] || ([ -z "$users" ] && [ -z "$groups" ]) || [ -z "$compute_nodes" ]; then + echo echo "Please provides options for both of these programs : " + python3 ./prepare_container.py --help + python3 ./utils/ship_a_key.py --help + exit 1 + fi } # Cleanup spire-agent generated files end_entrypoint() { - if ! [ -n "$encrypted" ]; then - echo "No encryption, nothing to clean" - else - echo "Cleaning everything before leaving ..." - rm -rf /tmp/data - rm /tmp/agent* - rm -f /tmp/keys - rm /tmp/dataset_info.yaml - kill "$1" - fi - exit "$2" + if ! [ -n "$encrypted" ]; then + echo "No encryption, nothing to clean" + else + echo "Cleaning everything before leaving ..." + rm -rf /tmp/data + rm /tmp/agent* + rm -f /tmp/keys + rm /tmp/dataset_info.yaml + kill "$1" + fi + exit "$2" } # Colors for prints @@ -65,15 +107,17 @@ echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Entering ent # if [ -n "$encrypted" ]; then - echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Encryption mode is on. Registering and running SPIRE Agent" + echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Encryption mode is on. Registering and running SPIRE Agent" - python3 ./utils/spawn_agent.py --config $config > /dev/null 2> /dev/null & - spire_agent_pid=$! + python3 ./utils/spawn_agent.py --config $config >/dev/null 2>/dev/null & + spire_agent_pid=$! fi - -ps $spire_agent_pid > /dev/null || ( echo "spire agent died, aborting" ; end_entrypoint "$spire_agent_pid" 1) +ps $spire_agent_pid >/dev/null || ( + echo "spire agent died, aborting" + end_entrypoint "$spire_agent_pid" 1 +) # ## [END] Perform node attestation @@ -86,9 +130,9 @@ echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Run containe # if [ -z "$encrypted" ]; then - python3 ./prepare_container.py -b "$base_oci_image" -s "$sif_path" -d "$docker_path" || end_entrypoint "$spire_agent_pid" 1 + python3 ./prepare_container.py -b "$base_oci_image" -s "$sif_path" -d "$docker_path" || end_entrypoint "$spire_agent_pid" 1 else - python3 ./prepare_container.py -e -b "$base_oci_image" -s "$sif_path" -d "$docker_path" || end_entrypoint "$spire_agent_pid" 1 + python3 ./prepare_container.py -e -b "$base_oci_image" -s "$sif_path" -d "$docker_path" || end_entrypoint "$spire_agent_pid" 1 fi # @@ -102,28 +146,27 @@ echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Container pr # if [ -n "$encrypted" ]; then - spiffeID=$(spire-agent api fetch --output json -socketPath /tmp/agent.sock | jq '.svids[0].spiffe_id' -r) + spiffeID=$(spire-agent api fetch --output json -socketPath /tmp/agent.sock | jq '.svids[0].spiffe_id' -r) fi - if [ -z "$encrypted" ]; then - echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Encryption mode is off, nothing to do" + echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Encryption mode is off, nothing to do" else - echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Encryption mode is on, writing key to the vault, using spiffeID $spiffeID" - - if [ -z "$users" ]; then - # If the user provided only groups - python3 ./utils/ship_a_key.py --config $config --username "$username" -g "$groups" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 - elif [ -z "$groups" ] ; then - # If the user provided only users - python3 ./utils/ship_a_key.py --config $config --username "$username" -u "$users" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 - else - # If the user provided both - python3 ./utils/ship_a_key.py --config $config --username "$username" -u "$users" -g "$groups" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 - fi - - echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Key written to the vault" + echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Encryption mode is on, writing key to the vault, using spiffeID $spiffeID" + + if [ -z "$users" ]; then + # If the user provided only groups + python3 ./utils/ship_a_key.py --config $config --username "$username" -g "$groups" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 + elif [ -z "$groups" ]; then + # If the user provided only users + python3 ./utils/ship_a_key.py --config $config --username "$username" -u "$users" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 + else + # If the user provided both + python3 ./utils/ship_a_key.py --config $config --username "$username" -u "$users" -g "$groups" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 + fi + + echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Container preparation]${NC} Key written to the vault" fi # diff --git a/client/container_preparation/input_logic/run.sh b/client/container_preparation/input_logic/run.sh index 840e2e0..86a1a34 100755 --- a/client/container_preparation/input_logic/run.sh +++ b/client/container_preparation/input_logic/run.sh @@ -7,13 +7,13 @@ PATH="$PATH:/sd-container/tools/input_logic/" echo "[SD-Container][Input-Logic] : Getting data decryption key from vault" # Get token via vault login. The data_login environment variable need to be exported from calling script -data_token=$(curl -s --request POST --data "$data_login" $vault/v1/auth/jwt/login | jq '.auth.client_token' -r) || exit 1 +data_token=$(curl -s --request POST --data "$data_login" $vault/v1/auth/jwt/login | jq '.auth.client_token' -r) || exit 1 # Use the token to access the key. The data_path environment variable needs to be exported from calling script -data_key=$(curl -s -H "X-Vault-Token: $data_token" $vault/v1/kv/data/${data_path} | jq '.data.data.key' -r) || exit 1 +data_key=$(curl -s -H "X-Vault-Token: $data_token" $vault/v1/kv/data/${data_path} | jq '.data.data.key' -r) || exit 1 # Write the key in an encrypted volume -echo "$data_key" > /sd-container/encrypted/decryption_key +echo "$data_key" >/sd-container/encrypted/decryption_key echo "[SD-Container][Input-Logic] : Decrypting data with the key from the vault" @@ -27,12 +27,12 @@ echo "[SD-Container][Input-Logic] : Data decrypted" # Untar the not anymore encrypted archive cd /sd-container/encrypted -tar xvf /sd-container/encrypted/decrypted_data.tgz || exit 1 +tar xvf /sd-container/encrypted/decrypted_data.tgz || exit 1 echo "[SD-Container][Input-Logic] : Data untared" -for input_logic_script in $(find /sd-container/tools/input_logic | grep ".sh" | grep -v "run.sh"); do - echo "[SD-Container][Input-Logic] : Running ${input_logic_script}" - $input_logic_script || echo "${input_logic_script} failed, aborting." - echo "[SD-Container][Input-Logic] : End of ${input_logic_script}" +for input_logic_script in $(find /sd-container/tools/input_logic | grep ".sh" | grep -v "run.sh"); do + echo "[SD-Container][Input-Logic] : Running ${input_logic_script}" + $input_logic_script || echo "${input_logic_script} failed, aborting." + echo "[SD-Container][Input-Logic] : End of ${input_logic_script}" done diff --git a/client/container_preparation/lib/image_build.py b/client/container_preparation/lib/image_build.py index d38eb85..3fc4134 100644 --- a/client/container_preparation/lib/image_build.py +++ b/client/container_preparation/lib/image_build.py @@ -145,7 +145,7 @@ def create_sif_image( user=os.getuid(), group_add=[f"{os.stat(docker_socket_path).st_gid}"], ) - + # Keeping the if/else for easy code rollback if encrypted containers can be used (that's why we don't have only one "docker_client.containers.run()") else: docker_client.containers.run( diff --git a/client/container_preparation/output_logic/out.sh b/client/container_preparation/output_logic/out.sh index 3d1d27b..c5bd23d 100755 --- a/client/container_preparation/output_logic/out.sh +++ b/client/container_preparation/output_logic/out.sh @@ -1,4 +1,3 @@ #!/bin/sh - echo "Some output logic runs here" diff --git a/client/container_preparation/output_logic/run.sh b/client/container_preparation/output_logic/run.sh index 5d2e204..8771b0c 100755 --- a/client/container_preparation/output_logic/run.sh +++ b/client/container_preparation/output_logic/run.sh @@ -1,9 +1,9 @@ #!/bin/sh for input_logic_script in $(find /sd-container/tools/output_logic | grep ".sh" | grep -v "run.sh"); do - echo "[SD-Container][Output-Logic] : Running ${input_logic_script}" - $input_logic_script || echo "${input_logic_script} failed, aborting." - echo "[SD-Container][Output-Logic] : End of ${input_logic_script}" + echo "[SD-Container][Output-Logic] : Running ${input_logic_script}" + $input_logic_script || echo "${input_logic_script} failed, aborting." + echo "[SD-Container][Output-Logic] : End of ${input_logic_script}" done PATH="$PATH:/sd-container/tools/input_logic/" diff --git a/client/container_preparation/tools/docker/docker_utils.py b/client/container_preparation/tools/docker/docker_utils.py index 9b08783..dfed1ee 100644 --- a/client/container_preparation/tools/docker/docker_utils.py +++ b/client/container_preparation/tools/docker/docker_utils.py @@ -1,7 +1,7 @@ import docker, os -def check_build_env_exists(docker_client : docker.DockerClient): +def check_build_env_exists(docker_client: docker.DockerClient): """Verify that the build environment (docker image sd-container/build_env) exists. Returns: @@ -14,7 +14,7 @@ def check_build_env_exists(docker_client : docker.DockerClient): return False -def build_build_env(docker_client : docker.DockerClient): +def build_build_env(docker_client: docker.DockerClient): """Builds the build environment""" docker_client.images.build( path=f"{os.path.realpath(os.path.dirname(__file__))}/build_env", diff --git a/client/data_preparation/Dockerfile b/client/data_preparation/Dockerfile index 9c34410..c880006 100644 --- a/client/data_preparation/Dockerfile +++ b/client/data_preparation/Dockerfile @@ -1,41 +1,39 @@ # Using Python original Docker image -FROM --platform=linux/amd64 python:3.9-alpine - -# Install necessary packages -RUN apk add \ - git \ - curl \ - jq \ - build-base \ - libffi-dev - -# Install Rust -RUN curl https://sh.rustup.rs -sSf -o rustup.sh ; chmod +x rustup.sh ; ./rustup.sh -y +ARG BUILDPLATFORM=linux/amd64 +FROM --platform=$BUILDPLATFORM python:3.9-alpine + +# Install necessary packages, spire-agent and rust +RUN apk add --no-cache \ + git=2.43.0-r0 \ + curl=8.5.0-r0 \ + jq=1.7.1-r0 \ + build-base=0.5-r3 \ + libffi-dev=3.4.4-r3 && \ +curl -LsSf -o spire-1.9.0-linux-amd64-musl.tar.gz https://github.com/spiffe/spire/releases/download/v1.9.0/spire-1.9.0-linux-amd64-musl.tar.gz && \ +tar xvf spire-1.9.0-linux-amd64-musl.tar.gz ; mv spire-1.9.0 /opt ; mv /opt/spire-1.9.0 /opt/spire && \ +ln -s /opt/spire/bin/spire-agent /usr/bin/spire-agent && \ +ln -s /opt/spire/bin/spire-server /usr/bin/spire-server && \ +rm -rf spire-1.9.0-linux-amd64-musl.tar.gz && \ +curl https://sh.rustup.rs -sSf -o rustup.sh ; chmod +x rustup.sh ; ./rustup.sh -y + +# Add rust binaries to PATH ENV PATH="$PATH:/root/.cargo/bin" -# Install spire-agent - -RUN wget -q https://github.com/spiffe/spire/releases/download/v1.9.1/spire-1.9.1-linux-amd64-musl.tar.gz -RUN tar xvf spire-1.9.1-linux-amd64-musl.tar.gz ; mv spire-1.9.1 /opt ; mv /opt/spire-1.9.1 /opt/spire -RUN ln -s /opt/spire/bin/spire-agent /usr/bin/spire-agent - -# Install pyspiffe package -RUN pip install git+https://github.com/HewlettPackard/py-spiffe.git@3640af9d6629c05e027f99010abc934cb74122a8 - # Create code directory, output directory RUN mkdir /data_preparation /output # Copy useful data from the project COPY ./client/data_preparation /data_preparation +# Set workdir +WORKDIR /data_preparation + # Install dependencies -RUN cd /data_preparation && pip install -r ./requirements.txt +RUN pip install --no-cache-dir -r ./requirements.txt && \ +pip install --no-cache-dir git+https://github.com/HewlettPackard/py-spiffe.git@3640af9d6629c05e027f99010abc934cb74122a8 # Copy utils for SPIFFEID creation ... COPY ./utils /data_preparation/utils -# Set workdir -WORKDIR /data_preparation - # Set entrypoint ENTRYPOINT [ "./entrypoint.sh" ] diff --git a/client/data_preparation/entrypoint.sh b/client/data_preparation/entrypoint.sh index 30cba89..6118bd2 100755 --- a/client/data_preparation/entrypoint.sh +++ b/client/data_preparation/entrypoint.sh @@ -5,40 +5,76 @@ # Argument parser, arguments for both Data preparation and key shipping should be handled here. parse_args() { - while [[ "$#" -gt 0 ]]; do - case "$1" in - --config) config="$2"; shift 2 ;; - -i|--input-data) input_data="$2"; shift 2 ;; - -o|--output-data) output_data="$2"; shift 2 ;; - --data-path) data_path="$2"; shift 2 ;; - --data-path-at-rest) data_path_at_rest="$2"; shift 2 ;; - --username) username="$2"; shift 2 ;; - -u|--users) users="$2" ; shift 2 ;; - -g|--groups) groups="$2" ; shift 2 ;; - -c|--compute-nodes) compute_nodes="$2" ; shift 2 ;; - -h|--help) python3 ./prepare_container.py --help ; python3 ./utils/ship_a_key.py --help ; exit 0 ;; - *) echo "Error: Unknown option $1"; python3 ./prepare_data.py --help ; python3 ./utils/ship_a_key.py --help ; exit 1 ;; - esac - done - - # Check for required arguments - if [ -z "$config" ] || [ -z "$input_data" ] || [ -z "$output_data" ] || [ -z "$data_path" ] || [ -z "$data_path_at_rest" ] || [ -z "$username" ] || ( [ -z "$users" ] && [ -z "$groups" ] ) || [ -z "$compute_nodes" ]; then - echo echo "Please provides options for both of these programs : " - python3 ./prepare_data.py --help - python3 ./utils/ship_a_key.py --help - exit 1 - fi + while [[ "$#" -gt 0 ]]; do + case "$1" in + --config) + config="$2" + shift 2 + ;; + -i | --input-data) + input_data="$2" + shift 2 + ;; + -o | --output-data) + output_data="$2" + shift 2 + ;; + --data-path) + data_path="$2" + shift 2 + ;; + --data-path-at-rest) + data_path_at_rest="$2" + shift 2 + ;; + --username) + username="$2" + shift 2 + ;; + -u | --users) + users="$2" + shift 2 + ;; + -g | --groups) + groups="$2" + shift 2 + ;; + -c | --compute-nodes) + compute_nodes="$2" + shift 2 + ;; + -h | --help) + python3 ./prepare_container.py --help + python3 ./utils/ship_a_key.py --help + exit 0 + ;; + *) + echo "Error: Unknown option $1" + python3 ./prepare_data.py --help + python3 ./utils/ship_a_key.py --help + exit 1 + ;; + esac + done + + # Check for required arguments + if [ -z "$config" ] || [ -z "$input_data" ] || [ -z "$output_data" ] || [ -z "$data_path" ] || [ -z "$data_path_at_rest" ] || [ -z "$username" ] || ([ -z "$users" ] && [ -z "$groups" ]) || [ -z "$compute_nodes" ]; then + echo echo "Please provides options for both of these programs : " + python3 ./prepare_data.py --help + python3 ./utils/ship_a_key.py --help + exit 1 + fi } # Cleanup spire-agent generated files end_entrypoint() { - echo "Cleaning everything before leaving ..." - rm -rf /tmp/data - rm /tmp/agent* - rm -f /tmp/keys - rm /tmp/dataset_info.yaml - kill "$1" - exit "$2" + echo "Cleaning everything before leaving ..." + rm -rf /tmp/data + rm /tmp/agent* + rm -f /tmp/keys + rm /tmp/dataset_info.yaml + kill "$1" + exit "$2" } # Colors for prints @@ -58,20 +94,18 @@ echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Data preparation]${NC} Entering entrypoi echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Data preparation]${NC} Registering and running SPIRE Agent" -python3 ./utils/spawn_agent.py --config $config > /dev/null 2> /dev/null & +python3 ./utils/spawn_agent.py --config $config >/dev/null 2>/dev/null & spire_agent_pid=$! -until [ -e /tmp/agent.sock ] -do - echo -e "${RED}[LUMI-SD][Data preparation] Spire workload api socket doesn't exist, waiting 10 seconds ${NC}" - sleep 10 - if ! ps | grep $spire_agent_pid > /dev/null ; then - echo "spire agent died, aborting" - end_entrypoint "$spire_agent_pid" 1 - fi +until [ -e /tmp/agent.sock ]; do + echo -e "${RED}[LUMI-SD][Data preparation] Spire workload api socket doesn't exist, waiting 10 seconds ${NC}" + sleep 10 + if ! ps | grep $spire_agent_pid >/dev/null; then + echo "spire agent died, aborting" + end_entrypoint "$spire_agent_pid" 1 + fi done - # ## [END] Perform node attestation # @@ -96,19 +130,18 @@ echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Data preparation]${NC} Data preparation spiffeID=$(spire-agent api fetch --output json -socketPath /tmp/agent.sock | jq '.svids[0].spiffe_id' -r) - echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Data preparation]${NC} Writing key to the vault, using spiffeID $spiffeID" # Handle different cases of user provided compute nodes / user / groups if [ -z "$users" ]; then - # If the user provided only groups - python3 ./utils/ship_a_key.py --config $config --username "$username" -g "$groups" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 -elif [ -z "$groups" ] ; then - # If the user provided only users - python3 ./utils/ship_a_key.py --config $config --username "$username" -u "$users" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 + # If the user provided only groups + python3 ./utils/ship_a_key.py --config $config --username "$username" -g "$groups" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 +elif [ -z "$groups" ]; then + # If the user provided only users + python3 ./utils/ship_a_key.py --config $config --username "$username" -u "$users" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 else - # If the user provided both - python3 ./utils/ship_a_key.py --config $config --username "$username" -u "$users" -g "$groups" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 + # If the user provided both + python3 ./utils/ship_a_key.py --config $config --username "$username" -u "$users" -g "$groups" -c "$compute_nodes" --data-path "$data_path" --data-path-at-rest "$data_path_at_rest" -i "$spiffeID" || end_entrypoint "$spire_agent_pid" 1 fi echo -e "${YELLOW}[LUMI-SD]${NC}${BLUE}[Data preparation]${NC} Key written to the vault" diff --git a/client/job_preparation/Dockerfile b/client/job_preparation/Dockerfile index a5ee06b..56799c8 100644 --- a/client/job_preparation/Dockerfile +++ b/client/job_preparation/Dockerfile @@ -1,29 +1,28 @@ # Using Python original Docker image -FROM --platform=linux/amd64 python:3.9-alpine +ARG BUILDPLATFORM=linux/amd64 +FROM --platform=$BUILDPLATFORM python:3.9-alpine -# Install necessary packages -RUN apk add \ - curl \ - build-base \ - libffi-dev +# Install necessary packages and rust +RUN apk add --no-cache \ + curl=8.5.0-r0 \ + build-base=0.5-r3 \ + libffi-dev=3.4.4-r3 && \ +curl https://sh.rustup.rs -sSf -o rustup.sh ; chmod +x rustup.sh ; ./rustup.sh -y -RUN curl https://sh.rustup.rs -sSf -o rustup.sh ; chmod +x rustup.sh ; ./rustup.sh -y +# Add rust binaries to PATH ENV PATH="$PATH:/root/.cargo/bin" -# Create code directory, output directory -RUN mkdir /job_preparation - # Copy useful data from the project COPY ./client/job_preparation /job_preparation +# Set workdir +WORKDIR /job_preparation + # Copy utils for SPIFFEID creation ... COPY ./utils /job_preparation/utils # Install dependencies -RUN cd /job_preparation && pip install -r ./requirements.txt - -# Set workdir -WORKDIR /job_preparation +RUN pip install --no-cache-dir -r ./requirements.txt # Set entrypoint -ENTRYPOINT [ "python3", "./prepare_job.py" ] \ No newline at end of file +ENTRYPOINT [ "python3", "./prepare_job.py" ] diff --git a/client/job_preparation/lib/info_file.py b/client/job_preparation/lib/info_file.py index 11ddd63..cb5e1ba 100644 --- a/client/job_preparation/lib/info_file.py +++ b/client/job_preparation/lib/info_file.py @@ -42,7 +42,7 @@ def get_info_from_infofile(ssh_client: SSHClient, path: str): def parse_info_file(ssh_client: SSHClient, path: str): - """Read an info file and parse it as a Python object + """Read an info file and parse it as a Python object Args: ssh_client (SSHClient): ssh client to use to read info file diff --git a/client/job_preparation/lib/sbatch_generation.py b/client/job_preparation/lib/sbatch_generation.py index 5d6d23d..5ea1e6d 100644 --- a/client/job_preparation/lib/sbatch_generation.py +++ b/client/job_preparation/lib/sbatch_generation.py @@ -11,7 +11,7 @@ def sbatch_from_template(options: argparse.Namespace, template_path: str) -> str Returns: str: path to the generated sbtach file """ - + # Replace placeholders in template str sbatch = boostrap_from_template(options, template_path) @@ -35,7 +35,7 @@ def boostrap_from_template(options: argparse.Namespace, template_path: str) -> s """ with open(template_path, "r") as sbatchfile: sbatch = sbatchfile.read() - + # Add general info sbatch = sbatch.replace("JOB_NAME", options.job_name.replace("/", "_")) sbatch = sbatch.replace("NODES", options.nodes) @@ -63,7 +63,6 @@ def boostrap_from_template(options: argparse.Namespace, template_path: str) -> s "APPLICATION_SECRET_PATH", options.application_secret_path ) - # Singularity info if options.singularity_supplementary_flags: sbatch = sbatch.replace( diff --git a/client/job_preparation/prepare_job.py b/client/job_preparation/prepare_job.py index ce2643d..11a0daf 100644 --- a/client/job_preparation/prepare_job.py +++ b/client/job_preparation/prepare_job.py @@ -16,12 +16,12 @@ # Parse configuration configuration = parse_configuration(options.config) - + # Parse configuration as options - options.username = configuration['supercomputer']['username'] - options.trust_domain = configuration['spire-server']['trust-domain'] - options.vault_address = configuration['vault']['url'] - + options.username = configuration["supercomputer"]["username"] + options.trust_domain = configuration["spire-server"]["trust-domain"] + options.vault_address = configuration["vault"]["url"] + # Check arguments options = check_arguments(options) @@ -51,7 +51,7 @@ # Copy SBATCH to supercomputer ssh_copy_file(ssh_client, sbatch_path, f"~/") - + # Copy config file to supercomputer ssh_copy_file(ssh_client, options.config, f"~/.config/hpcs-client.conf") @@ -105,7 +105,7 @@ print( f"Waiting for the job to run. You can now exit this script if needed, outputs will be available in {options.workdir}/output when finished" ) - + # Specific output format squeue command to parse informations about submitted job command = f"squeue -o '%A;%u;%T' | grep {options.username} | grep {jobid}" @@ -114,7 +114,7 @@ job_status = "" stdout = stdout.read().decode().replace("\n", "") stdin.close() - + # While job runs (while it has an entry in squeue) while stdout != "": # If status has changed diff --git a/client/job_preparation/utils/sbatch.template b/client/job_preparation/utils/sbatch.template index 1dcf1ab..f3c5120 100644 --- a/client/job_preparation/utils/sbatch.template +++ b/client/job_preparation/utils/sbatch.template @@ -75,7 +75,7 @@ fi if ! which gocryptfs ; then mkdir -p bin curl -O -L https://github.com/rfjakob/gocryptfs/releases/download/v2.4.0/gocryptfs_v2.4.0_linux-static_amd64.tar.gz || exit 1 - tar xvf gocryptfs_v2.4.0_linux-static_amd64.tar.gz + tar xvf gocryptfs_v2.4.0_linux-static_amd64.tar.gz mv gocryptfs ./bin/ rm -r gocryptfs* fi diff --git a/server/Dockerfile b/server/Dockerfile index a2bc5e3..da5ff95 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,32 +1,30 @@ # Using Python original Docker image -FROM --platform=linux/amd64 python:3.9-alpine - -RUN apk add \ - git \ - build-base \ - openssl - -# Install spire-agent -RUN wget -q https://github.com/spiffe/spire/releases/download/v1.9.0/spire-1.9.0-linux-amd64-musl.tar.gz -RUN tar xvf spire-1.9.0-linux-amd64-musl.tar.gz ; mv spire-1.9.0 /opt ; mv /opt/spire-1.9.0 /opt/spire -RUN ln -s /opt/spire/bin/spire-agent /usr/bin/spire-agent -RUN ln -s /opt/spire/bin/spire-server /usr/bin/spire-server - -# Install pyspiffe package -RUN pip install git+https://github.com/HewlettPackard/py-spiffe.git@3640af9d6629c05e027f99010abc934cb74122a8 +ARG BUILDPLATFORM=linux/amd64 +FROM --platform=$BUILDPLATFORM python:3.9-alpine + +# Install necessary packages and spire-agent +RUN apk add --no-cache \ + git=2.43.0-r0 \ + build-base=0.5-r3 \ + openssl=3.1.4-r5 && \ +wget -q https://github.com/spiffe/spire/releases/download/v1.9.0/spire-1.9.0-linux-amd64-musl.tar.gz && \ +tar xvf spire-1.9.0-linux-amd64-musl.tar.gz ; mv spire-1.9.0 /opt ; mv /opt/spire-1.9.0 /opt/spire && \ +ln -s /opt/spire/bin/spire-agent /usr/bin/spire-agent && \ +ln -s /opt/spire/bin/spire-server /usr/bin/spire-server && \ +rm -rf spire-1.9.0-linux-amd64-musl.tar.gz # Copy server -RUN mkdir /server COPY ./server /server +# Set workdir +WORKDIR /server + # Install dependencies -RUN cd /server && pip install -r ./requirements.txt +RUN pip install --no-cache-dir -r ./requirements.txt && \ +pip install --no-cache-dir git+https://github.com/HewlettPackard/py-spiffe.git@3640af9d6629c05e027f99010abc934cb74122a8 # Copy utils COPY ./utils /server/utils -# Set workdir -WORKDIR /server - # Set entrypoint -ENTRYPOINT [ "./entrypoint.sh" ] \ No newline at end of file +ENTRYPOINT [ "./entrypoint.sh" ] diff --git a/server/app.py b/server/app.py index 6a34863..69b0fb3 100644 --- a/server/app.py +++ b/server/app.py @@ -6,7 +6,7 @@ get_server_identity_JWT, validate_client_JWT_SVID, ) -from lib import spire_interactions +from lib import spire_interactions from tools.docker_utils import get_build_env_image_digests from pyspiffe.spiffe_id.spiffe_id import SpiffeId @@ -25,19 +25,25 @@ options = parse_arguments() configuration = parse_configuration(options.config) -if configuration['spire-server'].get('spire-server-bin') : - spire_interactions.spire_server_bin = configuration['spire-server']['spire-server-bin'] +if configuration["spire-server"].get("spire-server-bin"): + spire_interactions.spire_server_bin = configuration["spire-server"][ + "spire-server-bin" + ] -if configuration['spire-server'].get('pre-command') : - spire_interactions.pre_command = configuration['spire-server']['pre-command'] - if configuration['spire-server']['pre-command'] == "\"\"": +if configuration["spire-server"].get("pre-command"): + spire_interactions.pre_command = configuration["spire-server"]["pre-command"] + if configuration["spire-server"]["pre-command"] == '""': spire_interactions.pre_command = "" - + # Defining the trust domain (SPIRE Trust Domain) -trust_domain = configuration['spire-server']['trust-domain'] +trust_domain = configuration["spire-server"]["trust-domain"] # Perform vault login, to be able to run later operations against vault -hvac_client = vault_login(configuration['vault']['url'], get_server_identity_JWT(), configuration['vault']['server-role']) +hvac_client = vault_login( + configuration["vault"]["url"], + get_server_identity_JWT(), + configuration["vault"]["server-role"], +) # Dummy endpoint that handles the registration of compute nodes. @@ -101,9 +107,7 @@ async def handle_client_registration(): # Create a spiffeID for the workloads on the client. # Register workloads that have to run on this agent - workload_spiffeID = SpiffeId( - f"spiffe://{trust_domain}/c/{client_id}/workload" - ) + workload_spiffeID = SpiffeId(f"spiffe://{trust_domain}/c/{client_id}/workload") # Write the role bound to the workload's spiffeID write_client_role(hvac_client, f"client_{client_id}", workload_spiffeID) @@ -128,22 +132,34 @@ async def handle_client_registration(): "client_id": client_id, "token": agent_token, } - - # Spire-Agent binary + + # Spire-Agent binary result = entry_create( - agent_spiffeID, workload_spiffeID, ["unix:sha256:5ebff0fdb3335ec0221c35dcc7d3a4433eb8a5073a15a6dcfdbbb95bb8dbfa8e"] + agent_spiffeID, + workload_spiffeID, + [ + "unix:sha256:5ebff0fdb3335ec0221c35dcc7d3a4433eb8a5073a15a6dcfdbbb95bb8dbfa8e" + ], ) - - # Python 3.9 binary + + # Python 3.9 binary result = entry_create( - agent_spiffeID, workload_spiffeID, ["unix:sha256:956a50083eb7a58240fea28ac52ff39e9c04c5c74468895239b24bdf4760bffe"] + agent_spiffeID, + workload_spiffeID, + [ + "unix:sha256:956a50083eb7a58240fea28ac52ff39e9c04c5c74468895239b24bdf4760bffe" + ], ) - + # Qemu x86_64 (For docker mac) // Could add Rosetta binary result = entry_create( - agent_spiffeID, workload_spiffeID, ["unix:sha256:3fc6c8fbd8fe429b67276854fbb5ae594118f7f0b10352a508477833b04ee9b7"] + agent_spiffeID, + workload_spiffeID, + [ + "unix:sha256:3fc6c8fbd8fe429b67276854fbb5ae594118f7f0b10352a508477833b04ee9b7" + ], ) - + # Success return { "success": True, @@ -176,9 +192,7 @@ async def handle_workload_creation(): client_id = hashlib.sha256(client_id.encode()).hexdigest()[0:9] # Parse the spiffeID that will access the application - spiffeID = SpiffeId( - f"spiffe://{trust_domain}/c/{client_id}/s/{data['secret']}" - ) + spiffeID = SpiffeId(f"spiffe://{trust_domain}/c/{client_id}/s/{data['secret']}") # Check that the SVID correspond to the client_id (Can be removed if developper is certified) if validate_client_JWT_SVID(data["jwt"], client_id): diff --git a/server/entrypoint.sh b/server/entrypoint.sh index 93088da..5aac004 100755 --- a/server/entrypoint.sh +++ b/server/entrypoint.sh @@ -7,11 +7,11 @@ # Cleanup spire-agent generated files end_entrypoint() { - echo "Cleaning everything before leaving ..." - rm -rf /tmp/data - rm -r /tmp/spire-agent - kill "$1" - exit "$2" + echo "Cleaning everything before leaving ..." + rm -rf /tmp/data + rm -r /tmp/spire-agent + kill "$1" + exit "$2" } # Reset spire data everytime @@ -21,15 +21,14 @@ rm -rf /tmp/data spire-agent run -config /tmp/agent.conf || end_entrypoint 0 1 & spire_agent_pid=$! -agent_socket_path=$(cat /tmp/agent.conf | grep "socket_path" | cut -d "=" -f2 | cut -d "\"" -f1) +agent_socket_path=$(cat /tmp/agent.conf | grep "socket_path" | cut -d "=" -f2 | cut -d '"' -f1) sleep 10 -until [ -e $agent_socket_path ] -do - echo -e "${RED}[LUMI-SD][Data preparation] Spire workload api socket doesn't exist, waiting 10 seconds ${NC}" - sleep 10 +until [ -e $agent_socket_path ]; do + echo -e "${RED}[LUMI-SD][Data preparation] Spire workload api socket doesn't exist, waiting 10 seconds ${NC}" + sleep 10 done python3 ./app.py || end_entrypoint $spire_agent_pid 1 -end_entrypoint $spire_agent_pid 0 \ No newline at end of file +end_entrypoint $spire_agent_pid 0 diff --git a/server/lib/spire_interactions.py b/server/lib/spire_interactions.py index f7bdf82..d6fc428 100644 --- a/server/lib/spire_interactions.py +++ b/server/lib/spire_interactions.py @@ -8,10 +8,10 @@ pre_command = "microk8s.kubectl exec -n spire spire-server-0 --" -jwt_workload_api = default_jwt_source.DefaultJwtSource( +jwt_workload_api = default_jwt_source.DefaultJwtSource( workload_api_client=None, spiffe_socket_path="unix:///tmp/spire-agent/public/api.sock", - timeout_in_seconds=None + timeout_in_seconds=None, ) @@ -33,7 +33,7 @@ def token_generate(spiffeID: SpiffeId) -> subprocess.CompletedProcess: command = f"{spire_server_bin} token generate -spiffeID {str(spiffeID)}".split( " " ) - + return subprocess.run(command, capture_output=True) diff --git a/server/tools/cli/cli.py b/server/tools/cli/cli.py index 15f3eef..3b398e1 100644 --- a/server/tools/cli/cli.py +++ b/server/tools/cli/cli.py @@ -1,5 +1,6 @@ import argparse + # Parse arguments from the cli def parse_arguments(): """Parse arguments from cli @@ -17,4 +18,4 @@ def parse_arguments(): help="Configuration file (INI Format) (default: /tmp/hpcs-server.conf)", ) - return parser.parse_args() \ No newline at end of file + return parser.parse_args() diff --git a/server/tools/config/config.py b/server/tools/config/config.py index 98d722b..7280a74 100644 --- a/server/tools/config/config.py +++ b/server/tools/config/config.py @@ -1,19 +1,24 @@ from configparser import ConfigParser, NoSectionError, NoOptionError -def parse_configuration(path : str): + +def parse_configuration(path: str): config = ConfigParser() config.read(path) - - if not 'spire-server' in config: + + if not "spire-server" in config: raise NoSectionError("spire-server section missing, aborting") - - if not 'vault' in config: + + if not "vault" in config: raise NoSectionError("vault section missing, aborting") - - if not 'address' in config['spire-server'] or not 'port' in config['spire-server'] or not 'trust-domain' in config['spire-server']: + + if ( + not "address" in config["spire-server"] + or not "port" in config["spire-server"] + or not "trust-domain" in config["spire-server"] + ): raise NoOptionError("'spire-server' section is incomplete, aborting") - - if not 'url' in config['vault'] or not 'server-role' in config['vault']: + + if not "url" in config["vault"] or not "server-role" in config["vault"]: raise NoOptionError("'vault' section is incomplete, aborting") - - return config \ No newline at end of file + + return config diff --git a/utils/conf/client/conf.py b/utils/conf/client/conf.py index f295d27..1b4f730 100644 --- a/utils/conf/client/conf.py +++ b/utils/conf/client/conf.py @@ -1,32 +1,54 @@ # Parse configuration file from configparser import ConfigParser, NoSectionError, NoOptionError -def parse_configuration(path : str): + +def parse_configuration(path: str): config = ConfigParser() config.read(path) - - if not 'supercomputer' in config: - raise NoSectionError("supercomputer section missing in configuration file, aborting") - - if not 'spire-server' in config: - raise NoSectionError("hpcs-server section missing in configuration file, aborting") - - if not 'hpcs-server' in config: - raise NoSectionError("hpcs-server section missing in configuration file, aborting") - - if not 'vault' in config: + + if not "supercomputer" in config: + raise NoSectionError( + "supercomputer section missing in configuration file, aborting" + ) + + if not "spire-server" in config: + raise NoSectionError( + "hpcs-server section missing in configuration file, aborting" + ) + + if not "hpcs-server" in config: + raise NoSectionError( + "hpcs-server section missing in configuration file, aborting" + ) + + if not "vault" in config: raise NoSectionError("vault section missing in configuration file, aborting") - - if not 'address' in config['supercomputer'] or not 'username' in config['supercomputer']: - raise NoOptionError("'spire-server' section is incomplete in configuration file, aborting") - - if not 'address' in config['spire-server'] or not 'port' in config['spire-server'] or not 'trust-domain' in config['spire-server']: - raise NoOptionError("'spire-server' section is incomplete in configuration file, aborting") - - if not 'url' in config['hpcs-server']: - raise NoOptionError("'hpcs-server' section is incomplete in configuration file, aborting") - - if not 'url' in config['vault']: - raise NoOptionError("'vault' section is incomplete in configuration file, aborting") - - return config \ No newline at end of file + + if ( + not "address" in config["supercomputer"] + or not "username" in config["supercomputer"] + ): + raise NoOptionError( + "'spire-server' section is incomplete in configuration file, aborting" + ) + + if ( + not "address" in config["spire-server"] + or not "port" in config["spire-server"] + or not "trust-domain" in config["spire-server"] + ): + raise NoOptionError( + "'spire-server' section is incomplete in configuration file, aborting" + ) + + if not "url" in config["hpcs-server"]: + raise NoOptionError( + "'hpcs-server' section is incomplete in configuration file, aborting" + ) + + if not "url" in config["vault"]: + raise NoOptionError( + "'vault' section is incomplete in configuration file, aborting" + ) + + return config diff --git a/utils/ship_a_key.py b/utils/ship_a_key.py index 0936f7a..36967dd 100644 --- a/utils/ship_a_key.py +++ b/utils/ship_a_key.py @@ -242,8 +242,8 @@ def create_authorized_workloads( if __name__ == "__main__": # Parse arguments from CLI - options = parse_arguments() - + options = parse_arguments() + # Parse configuration file configuration = parse_configuration(options.config) @@ -281,7 +281,9 @@ def create_authorized_workloads( ) # Login to the vault using client's certificate - hvac_client = vault_login(configuration["vault"]["url"], SVID, f"client_{client_id}") + hvac_client = vault_login( + configuration["vault"]["url"], SVID, f"client_{client_id}" + ) # Prepare secret secret = {} diff --git a/utils/spawn_agent.py b/utils/spawn_agent.py index d58a2c9..d76a0bf 100644 --- a/utils/spawn_agent.py +++ b/utils/spawn_agent.py @@ -34,6 +34,7 @@ def parse_arguments(): return parser.parse_args() + def get_token(url, compute_node_token: bool): """Get joinToken to perform node registration from server @@ -68,25 +69,23 @@ def get_token(url, compute_node_token: bool): if __name__ == "__main__": # Get arguments options = parse_arguments() - + # Parse configuration file configuration = parse_configuration(options.config) # Get token from API - token = get_token( - configuration['hpcs-server']['url'], options.compute_node - ) + token = get_token(configuration["hpcs-server"]["url"], options.compute_node) # Overwrite configuration template agent_configuration_template = open("./utils/agent-on-the-fly.conf").read() agent_configuration_template = agent_configuration_template.replace( - "SPIRE_TRUST_DOMAIN", configuration['spire-server']['trust-domain'] + "SPIRE_TRUST_DOMAIN", configuration["spire-server"]["trust-domain"] ) agent_configuration_template = agent_configuration_template.replace( - "SPIRE_SERVER_ADDRESS", configuration['spire-server']['address'] + "SPIRE_SERVER_ADDRESS", configuration["spire-server"]["address"] ) agent_configuration_template = agent_configuration_template.replace( - "SPIRE_SERVER_PORT", configuration['spire-server']['port'] + "SPIRE_SERVER_PORT", configuration["spire-server"]["port"] ) agent_configuration_template = agent_configuration_template.replace( "SOCKETPATH", options.socketpath diff --git a/utils/ssh_utils.py b/utils/ssh_utils.py index a5038a6..5b0f35e 100644 --- a/utils/ssh_utils.py +++ b/utils/ssh_utils.py @@ -30,7 +30,7 @@ def ssh_connect(username: str) -> SSHClient: # Probably running in a container except SSHException: - pkey=RSAKey.from_private_key_file("/tmp/.ssh/id_rsa") + pkey = RSAKey.from_private_key_file("/tmp/.ssh/id_rsa") client.connect( host, port, diff --git a/utils/vault/vault_utils.py b/utils/vault/vault_utils.py index e05e843..a21ebe6 100644 --- a/utils/vault/vault_utils.py +++ b/utils/vault/vault_utils.py @@ -2,7 +2,8 @@ from pyspiffe.svid.jwt_svid import JwtSvid from pyspiffe.spiffe_id.spiffe_id import SpiffeId -def vault_login(url : str, SVID: JwtSvid, client_id) -> hvac.Client : + +def vault_login(url: str, SVID: JwtSvid, client_id) -> hvac.Client: """Login to vault Args: @@ -14,7 +15,7 @@ def vault_login(url : str, SVID: JwtSvid, client_id) -> hvac.Client : return client -def write_client_policy(client : hvac.Client, client_id: str): +def write_client_policy(client: hvac.Client, client_id: str): """Write a client write-only policy to vault Args: @@ -29,7 +30,7 @@ def write_client_policy(client : hvac.Client, client_id: str): return client.sys.create_or_update_acl_policy(name=f"{client_id}", policy=policy) -def write_client_role(client : hvac.Client, client_id: str, spiffeID: SpiffeId): +def write_client_role(client: hvac.Client, client_id: str, spiffeID: SpiffeId): """Write a client role, mapping a "clientID" named role to a spiffeID Args: @@ -47,7 +48,7 @@ def write_client_role(client : hvac.Client, client_id: str, spiffeID: SpiffeId): ) -def write_user_policy(client : hvac.Client, client_id: str, application: str): +def write_user_policy(client: hvac.Client, client_id: str, application: str): """Write a user read-only policy to vault Args: @@ -65,7 +66,9 @@ def write_user_policy(client : hvac.Client, client_id: str, application: str): ) -def write_user_role(client : hvac.Client, client_id: str, application: str, spiffeID: SpiffeId): +def write_user_role( + client: hvac.Client, client_id: str, application: str, spiffeID: SpiffeId +): """Write a user role bounding a spiffeID to the read-only policy accessing the client's secret Args: @@ -84,7 +87,7 @@ def write_user_role(client : hvac.Client, client_id: str, application: str, spif ) -def write_secret(client : hvac.Client, secrets_path: str, secret: any): +def write_secret(client: hvac.Client, secrets_path: str, secret: any): """Write a secret to the vault Args: