#!/bin/bash # Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 MY_VERSION="0.1.0" MY_NAME="enclavectl" MY_DESC="AWS Nitro Enclaves with K8s deployment tool" source "$(dirname $(realpath $0))/scripts/common.sh" # Configuration items CONFIG_NAMES=(region instance_type eks_cluster_name eks_worker_node_name eks_worker_node_capacity k8s_version node_enclave_cpu_limit node_enclave_memory_limit_mib) # Utility functions and definitions source "$SCRIPTS_DIR/utils.sh" # Scripts readonly CREATE_LAUNCH_TEMPLATE="00_create_launch_template.sh" readonly CREATE_EKS_CLUSTER="01_create_eks_cluster.sh" readonly ENABLE_DEVICE_PLUGIN="02_enable_device_plugin.sh" readonly BUILD_ENCLAVE_APPS="03_build_enclave_apps.sh" readonly BUILD_IMAGE="04_build_image.sh" readonly PUSH_IMAGE="05_push_image.sh" readonly RUN_APP="06_run_app.sh" readonly STOP_APP="07_stop_app.sh" readonly CLEANUP_RESOURCES="99_cleanup_resources.sh" USAGE="\ $MY_NAME v$MY_VERSION - $MY_DESC Usage: $(basename "$0") [arguments] Commands: configure Prepare the setup configuration --file The file containing the settings for configuration (i.e. settings.json) setup Setup a Nitro Enable enabled EKS cluster based on input configuration - Generates a basic EC2 Launch Template for Nitro Enclaves and UserData - Creates an EKS cluster with a managed node-group of configured capacity - Deploys the Nitro Enclaves K8s Device plugin build Build a Nitro Enclave based application for deployment --image The application image name. push Push the Nitro Enclaves application container to a remote auto-generated private ECR repository. --image The application image name run Generate the deployment specification for the Nitro Enclaves application and deploy it --image The application image name [--prepare-only] Only generate the application deployment specification file without deploying it stop Terminate the Nitro Enclaves with K8s application deployed via the 'run' command --image The application image name cleanup Clean up all the resources previously created via the 'setup' command [--force] Ignores errors and force cleans all resources and configuration " # Validate number of arguments given to a function. # validate_arg_count() { local arg_count=$1; shift for arg in "$@" do [[ "$arg_count" == "$arg" ]] && { return; } done die "Invalid arguments. Please use \`$MY_NAME help\` for help." } # Print usage # cmd_help() { say "$USAGE" } # Ensure basic dependencies of the project are installed. ensure_basic_deps() { which docker > /dev/null 2>&1 ok_or_die "docker not found. Aborting." \ "Please make sure you have docker installed. For more information, see" \ "https://docs.docker.com/desktop/install/linux-install" which jq > /dev/null 2>&1 ok_or_die "jq not found. Aborting." \ "Please make sure you have jq package installed." } # Ensure eksctl and kubectl are available on this deployment machine ensure_eks_deps() { which eksctl > /dev/null 2>&1 ok_or_die "eksctl not found. Aborting." \ "Please make sure you have eksctl installed. For more information, see" \ "https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html." which kubectl > /dev/null 2>&1 ok_or_die "kubectl not found. Aborting." \ "Please make sure you have kubectl installed. For more information, see" \ "https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html." } exec_subscript() { source "$1" shift main "$@" } apply_configuration() { local settings=$1 local json_name=$2 local ret truncate -s 0 "$WORKING_DIR/$FILE_CONFIGURATION"; ret=$? [[ $ret -eq 0 ]] && { for item in "${CONFIG_NAMES[@]}" do local value value=$(echo "$settings" | jq -r ".$item"); ret=$? [[ "$value" = "null" ]] && { say_err "$item value is not set in the $json_name file!"; ret=$FAILURE; rm -f "$WORKING_DIR/$FILE_CONFIGURATION" break } echo "CONFIG_${item^^}=\"$value\"" >> "$WORKING_DIR/$FILE_CONFIGURATION" done } return $ret } try_create_setup_uuid() { local uuid_pattern='^\{?[A-Z0-9a-z]{8}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{12}\}?$' CONFIG_SETUP_UUID="" # Try loading the UUID from file. [[ -f $WORKING_DIR/$FILE_SETUP_ID ]] && { CONFIG_SETUP_UUID=$(<"$WORKING_DIR/$FILE_SETUP_ID"); } [[ "${CONFIG_SETUP_UUID}" != "" ]] || { say "Setup UUID doesn't exist. Creating one..." CONFIG_SETUP_UUID=$(<"/proc/sys/kernel/random/uuid") } # Check if the UUID is valid. [[ $CONFIG_SETUP_UUID =~ $uuid_pattern ]] || { die "Your existing configuration seems corrupted!" \ "Run './$MY_NAME cleanup' to clean invalid setup configuration" \ "and try restarting demo setup. If you already created some resources, you" \ "need remove them manually." } echo "$CONFIG_SETUP_UUID" > "$WORKING_DIR/$FILE_SETUP_ID" ok_or_die "Cannot create session UUID file!" \ "Please ensure that you have write access to the project folder." say "Using setup UUID: $CONFIG_SETUP_UUID" } try_load_configuration() { [[ -f "$WORKING_DIR/$FILE_CONFIGURATION" ]] && { source "$WORKING_DIR/$FILE_CONFIGURATION" for item in "${CONFIG_NAMES[@]}" do local var_name="CONFIG_${item^^}" local value=${!var_name} [[ $value = "" || $value = "null" ]] && { say_warn "The configuration seems corrupted! Ignoring existing configuration..." rm -f "$WORKING_DIR/$FILE_CONFIGURATION" return } done } [[ -f "$WORKING_DIR/$FILE_CONFIGURATION" ]] && \ CONFIG_SETUP_UUID=$(<"$WORKING_DIR/$FILE_SETUP_ID"); } cmd_configure() { validate_arg_count $# 2 case $1 in --file) [ -f "$WORKING_DIR/$FILE_CONFIGURATION" ] && { say_warn "Project settings have already been configured." \ "To apply new settings, please clean up the resources first" \ "and try again." exit 0 } settings_file="$2"; ;; *) die "Invalid argument: $1. Please use \`$0 help\` for help.";; esac # Create a setup uuid. Load if it already exists. try_create_setup_uuid local settings settings=$(cat "$settings_file" 2> /dev/null) ok_or_die "Cannot open the settings file: $settings_file" echo "$settings" | jq '.' 2>&1 > /dev/null ok_or_die "Cannot parse the settings file." apply_configuration "$settings" "$settings_file" ok_or_die "Cannot create configuration from $settings_file!" say "Using configuration" echo "$settings" | jq '.' say "Configuration finished successfully." } cmd_setup() { validate_arg_count $# 0 say "Running setup..." # Nitro Enclave Launch Template exec_subscript "$SCRIPTS_DIR/$CREATE_LAUNCH_TEMPLATE" ok_or_die "Cannot create EC2 Launch Template." # EKS Cluster exec_subscript "$SCRIPTS_DIR/$CREATE_EKS_CLUSTER" ok_or_die "Cannot create EKS Cluster." # Enable Device Plugin exec_subscript "$SCRIPTS_DIR/$ENABLE_DEVICE_PLUGIN" ok_or_die "Error while enabling the device plugin." say "Done." } cmd_build() { validate_arg_count $# 2 case $1 in --image) exec_subscript "$SCRIPTS_DIR/$BUILD_ENCLAVE_APPS" "$2" ok_or_die "Cannot build enclave applications for $2!" exec_subscript "$SCRIPTS_DIR/$BUILD_IMAGE" "$2" ok_or_die "Cannot build docker image for $2!" ;; *) die "Invalid arguments. Please use \`$0 help\` for help." esac } cmd_push() { validate_arg_count $# 2 case $1 in --image) exec_subscript "$SCRIPTS_DIR/$PUSH_IMAGE" "$2" ok_or_die "Cannot push docker image for $2!" ;; *) die "Invalid arguments. Please use \`$0 help\` for help." esac } cmd_run() { validate_arg_count $# 2 3 local image="" local prepare_only=false while [[ $# -ge 1 ]] do case $1 in "--image") image=$2; shift; ;; "--prepare-only") prepare_only=true ;; *) die "Invalid arguments. Please use \`$0 help\` for help." esac shift; done exec_subscript "$SCRIPTS_DIR/$RUN_APP" "$image" "$prepare_only" ok_or_die "Error while running application!" } cmd_stop() { validate_arg_count $# 2 case $1 in --image) exec_subscript "$SCRIPTS_DIR/$STOP_APP" "$2" ok_or_die "Error while stopping the application!" ;; *) die "Invalid arguments. Please use \`$0 help\` for help." esac } cmd_cleanup() { validate_arg_count $# 0 1 local ignore_errors=false case $1 in --force) ignore_errors=true ;; "") ;; *) die "Invalid arguments. Please use \`$0 help\` for help." esac exec_subscript "$SCRIPTS_DIR/$CLEANUP_RESOURCES" "$ignore_errors" ok_or_die "Cannot clean resources due to previous errors." } main() { if [ "$#" -eq 0 ]; then cmd_help exit 1 fi # Ensure basic dependencies ensure_basic_deps # Try loading applied settings. try_load_configuration local cmd="$1" case "$1" in -h|help) cmd_help exit 1 ;; -c|configure) shift cmd_configure "$@" ;; *) declare -f "cmd_$cmd" > /dev/null ok_or_die "Unknown command: $1. Please use \`$MY_NAME help\` for help." case "$1" in setup|run|stop) ensure_eks_deps ;; esac [[ ! -f $WORKING_DIR/$FILE_CONFIGURATION ]] && \ die "The demo hasn't been configured yet. Please use \`$MY_NAME help\` to know how to configure." cmd_"$@" ;; esac } main "${@}"