diff --git a/src/build/front_wrapper.sh b/src/build/front_wrapper.sh index 9df7996d7..ad3c4ada9 100755 --- a/src/build/front_wrapper.sh +++ b/src/build/front_wrapper.sh @@ -41,4 +41,17 @@ az login --service-principal \ --allow-no-subscriptions \ --output none -. "${BASH_SOURCE%/*}/apply_tf.sh" "${1}" "${2}" "${3}" "${4}" "${5}" "${6}" "${7}" \ No newline at end of file +src_dir=$(dirname "$(realpath "${BASH_SOURCE%/*}")") + +# Create config resources given a subscription ID and terraform configuration folder path +create_tf_config() { + . "${src_dir}/scripts/config/config_create.sh" "${mlz_config}" "${1}" "${2}" +} + +# create backends for terraform modules +create_tf_config "${mlz_saca_subid}" "${src_dir}/core/saca-hub" +create_tf_config "${mlz_tier0_subid}" "${src_dir}/core/tier-0" +create_tf_config "${mlz_tier1_subid}" "${src_dir}/core/tier-1" +create_tf_config "${mlz_tier2_subid}" "${src_dir}/core/tier-2" + +. "${BASH_SOURCE%/*}/apply_tf.sh" "${1}" "${2}" "${3}" "${4}" "${5}" "${6}" "${7}" \ No newline at end of file diff --git a/src/core/globals.front.json b/src/core/globals.front.json index 207aea159..482574598 100644 --- a/src/core/globals.front.json +++ b/src/core/globals.front.json @@ -5,7 +5,7 @@ "varname": "tf_environment", "type": "text", "default_val": "env:TF_ENV", - "description": "Terraform deployment Environment https://www.terraform.io/docs/language/settings/backends/azurerm.html#environment", + "description": "Terraform azurerm environment (e.g. 'public') see: https://www.terraform.io/docs/language/settings/backends/azurerm.html#environment", "options": [] }, { @@ -19,21 +19,21 @@ "varname": "mlz_tenantid", "type": "text", "default_val": "env:TENANT_ID", - "description": "Tenant ID where your subscriptions liv", + "description": "Tenant ID where your subscriptions live", "options": [] }, { "varname": "mlz_metadatahost", "type": "text", "default_val": "management.azure.com", - "description": "Host for azure metadata: e.g 'management.azure.com' or 'management.usgovcloudapi.net'", + "description": "Azure Metadata Service endpoint. (e.g 'management.azure.com' or 'management.usgovcloudapi.net')", "options": [] }, { "varname": "mlz_location", "type": "text", "default_val": "env:MLZ_LOCATION", - "description": "The location that you're deploying to.", + "description": "The location that you're deploying to (e.g. 'eastus')", "options": [] } ] diff --git a/src/core/saca-hub/saca-hub.front.json b/src/core/saca-hub/saca-hub.front.json index fda365706..a7f1e4534 100644 --- a/src/core/saca-hub/saca-hub.front.json +++ b/src/core/saca-hub/saca-hub.front.json @@ -4,35 +4,35 @@ { "varname": "deploymentname", "type": "text", - "default_val": "mlzci", + "default_val": "mlz", "description": "A unique name for your terraform deployment", "options": [] }, { "varname": "saca_subid", "type": "text", - "default_val": "env:SUBSCRIPTION_ID", + "default_val": "env:HUB_SUBSCRIPTION_ID", "description": "The subscription id where the SACA hub lives", "options": [] }, { "varname": "saca_rgname", "type": "text", - "default_val": "rg-eastus-mlz-sacaci", + "default_val": "rg-eastus-mlz-saca", "description": "Resource group name", "options": [] }, { "varname": "saca_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-sacaci", + "default_val": "vn-eastus-mlz-saca", "description": "Virtual Network Name", "options": [] }, { "varname": "saca_lawsname", "type": "text", - "default_val": "laws-eastus-mlz-sacaci", + "default_val": "laws-eastus-mlz-saca", "description": "Name for log analytic workspace", "options": [] }, @@ -48,42 +48,42 @@ { "varname": "tier0_rgname", "type": "text", - "default_val": "rg-eastus-mlz-t0ci", + "default_val": "rg-eastus-mlz-t0", "description": "Tier 0 resource group name", "options": [] }, { "varname": "tier0_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-t0ci", + "default_val": "vn-eastus-mlz-t0", "description": "Tier 0 virtual network name", "options": [] }, { "varname": "tier1_rgname", "type": "text", - "default_val": "rg-eastus-mlz-t1ci", + "default_val": "rg-eastus-mlz-t1", "description": "Tier 1 resource group name", "options": [] }, { "varname": "tier1_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-t1ci", + "default_val": "vn-eastus-mlz-t1", "description": "Tier one virtual network name", "options": [] }, { "varname": "tier2_rgname", "type": "text", - "default_val": "rg-eastus-mlz-t1ci", + "default_val": "rg-eastus-mlz-t2", "description": "Tier 2 resource group name", "options": [] }, { "varname": "tier2_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-t2ci", + "default_val": "vn-eastus-mlz-t2", "description": "Tier 2 virtual network name", "options": [] }, diff --git a/src/core/tier-0/tier-0.front.json b/src/core/tier-0/tier-0.front.json index 21b32364f..24ed0b7af 100644 --- a/src/core/tier-0/tier-0.front.json +++ b/src/core/tier-0/tier-0.front.json @@ -7,56 +7,56 @@ { "varname": "saca_subid", "type": "text", - "default_val": "env:SUBSCRIPTION_ID", + "default_val": "env:HUB_SUBSCRIPTION_ID", "description": "Saca Hub Subscription ID", "options": [] }, { "varname": "saca_rgname", "type": "text", - "default_val": "rg-eastus-mlz-sacaci", + "default_val": "rg-eastus-mlz-saca", "description": "Saca Hub Resource Group Name", "options": [] }, { "varname": "saca_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-sacaci", + "default_val": "vn-eastus-mlz-saca", "description": "Saca Virtual Network Name", "options": [] }, { "varname": "saca_fwname", "type": "text", - "default_val": "DemoFirewallci", + "default_val": "DemoFirewall", "description": "Saca Firewall Name", "options": [] }, { "varname": "saca_lawsname", "type": "text", - "default_val": "laws-eastus-mlz-sacaci", + "default_val": "laws-eastus-mlz-saca", "description": "Saca Log Analytic Workspace Name", "options": [] }, { "varname": "tier0_subid", "type": "text", - "default_val": "env:SUBSCRIPTION_ID", + "default_val": "env:TIER0_SUBSCRIPTION_ID", "description": "Tier0 Subscription Id", "options": [] }, { "varname": "tier0_rgname", "type": "text", - "default_val": "rg-eastus-mlz-t0ci", + "default_val": "rg-eastus-mlz-t0", "description": "Tier0 Resource Group Name", "options": [] }, { "varname": "tier0_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-t0ci", + "default_val": "vn-eastus-mlz-t0", "description": "Tier0 Virtual Network Name", "options": [] }, @@ -125,7 +125,7 @@ { "varname": "subnets.{TIER0_SUBNETVM_NAME}.routetable_name", "type": "text", - "default_val": "tier0vmsrtci", + "default_val": "tier0vmsrt", "description": "Tier 0 Routeable Subnet Name", "options": [] } diff --git a/src/core/tier-1/tier-1.front.json b/src/core/tier-1/tier-1.front.json index 7ac628a78..61ef150db 100644 --- a/src/core/tier-1/tier-1.front.json +++ b/src/core/tier-1/tier-1.front.json @@ -7,56 +7,56 @@ { "varname": "saca_subid", "type": "text", - "default_val": "env:SUBSCRIPTION_ID", + "default_val": "env:HUB_SUBSCRIPTION_ID", "description": "Saca Hub Subscription ID", "options": [] }, { "varname": "saca_rgname", "type": "text", - "default_val": "rg-eastus-mlz-sacaci", + "default_val": "rg-eastus-mlz-saca", "description": "Saca Hub Resource Group Name", "options": [] }, { "varname": "saca_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-sacaci", + "default_val": "vn-eastus-mlz-saca", "description": "Saca Virtual Network Name", "options": [] }, { "varname": "saca_fwname", "type": "text", - "default_val": "DemoFirewallci", + "default_val": "DemoFirewall", "description": "Saca Firewall Name", "options": [] }, { "varname": "saca_lawsname", "type": "text", - "default_val": "laws-eastus-mlz-sacaci", + "default_val": "laws-eastus-mlz-saca", "description": "Saca Log Analytic Workspace Name", "options": [] }, { "varname": "tier1_subid", "type": "text", - "default_val": "env:SUBSCRIPTION_ID", + "default_val": "env:TIER1_SUBSCRIPTION_ID", "description": "Tier0 Subscription Id", "options": [] }, { "varname": "tier1_rgname", "type": "text", - "default_val": "rg-eastus-mlz-t1ci", + "default_val": "rg-eastus-mlz-t1", "description": "Tier0 Resource Group Name", "options": [] }, { "varname": "tier1_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-t1ci", + "default_val": "vn-eastus-mlz-t1", "description": "Tier0 Virtual Network Name", "options": [] }, @@ -125,7 +125,7 @@ { "varname": "subnets.{TIER1_SUBNETVM_NAME}.routetable_name", "type": "text", - "default_val": "tier1vmsrtci", + "default_val": "tier1vmsrt", "description": "Tier 0 Routeable Subnet Name", "options": [] } diff --git a/src/core/tier-2/tier-2.front.json b/src/core/tier-2/tier-2.front.json index f38ae0ed4..be8fd4ed1 100644 --- a/src/core/tier-2/tier-2.front.json +++ b/src/core/tier-2/tier-2.front.json @@ -7,56 +7,56 @@ { "varname": "saca_subid", "type": "text", - "default_val": "env:SUBSCRIPTION_ID", + "default_val": "env:HUB_SUBSCRIPTION_ID", "description": "Saca Hub Subscription ID", "options": [] }, { "varname": "saca_rgname", "type": "text", - "default_val": "rg-eastus-mlz-sacaci", + "default_val": "rg-eastus-mlz-saca", "description": "Saca Hub Resource Group Name", "options": [] }, { "varname": "saca_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-sacaci", + "default_val": "vn-eastus-mlz-saca", "description": "Saca Virtual Network Name", "options": [] }, { "varname": "saca_fwname", "type": "text", - "default_val": "DemoFirewallci", + "default_val": "DemoFirewall", "description": "Saca Firewall Name", "options": [] }, { "varname": "saca_lawsname", "type": "text", - "default_val": "laws-eastus-mlz-sacaci", + "default_val": "laws-eastus-mlz-saca", "description": "Saca Log Analytic Workspace Name", "options": [] }, { "varname": "tier2_subid", "type": "text", - "default_val": "env:SUBSCRIPTION_ID", + "default_val": "env:TIER2_SUBSCRIPTION_ID", "description": "Tier0 Subscription Id", "options": [] }, { "varname": "tier2_rgname", "type": "text", - "default_val": "rg-eastus-mlz-t2ci", + "default_val": "rg-eastus-mlz-t2", "description": "Tier2 Resource Group Name", "options": [] }, { "varname": "tier2_vnetname", "type": "text", - "default_val": "vn-eastus-mlz-t2ci", + "default_val": "vn-eastus-mlz-t2", "description": "Tier2 Virtual Network Name", "options": [] }, @@ -125,7 +125,7 @@ { "varname": "subnets.{TIER2_SUBNETVM_NAME}.routetable_name", "type": "text", - "default_val": "tier2vmsrtci", + "default_val": "tier2vmsrt", "description": "Tier 0 Routeable Subnet Name", "options": [] } diff --git a/src/docs/command-line-deployment.md b/src/docs/command-line-deployment.md index b56f85205..b4957f21b 100644 --- a/src/docs/command-line-deployment.md +++ b/src/docs/command-line-deployment.md @@ -48,7 +48,7 @@ The MLZ deployment architecture uses a single Service Principal whose credential chmod u+x src/scripts/mlz_tf_setup.sh - src/scripts/mlz_tf_setup.sh src/core/mlz_tf_cfg.var + src/scripts/mlz_tf_setup.sh src/mlz_tf_cfg.var ``` ### Set Terraform Configuration Variables diff --git a/src/docs/ui-deployment.md b/src/docs/ui-deployment.md index 7d715c30c..351e18151 100644 --- a/src/docs/ui-deployment.md +++ b/src/docs/ui-deployment.md @@ -1,6 +1,6 @@ # Mission LZ User Interface -The mission LZ front-end is designed to be a single stop for easily entering all of the configuration items that Terraform needs to deploy Mission LZ to a target set of subscriptions. +The mission LZ front-end is designed to be a single stop for easily entering all of the configuration items that Terraform needs to deploy Mission LZ to a target set of subscriptions. ## General Requirements @@ -11,11 +11,11 @@ For any of the following options you will need docker on your machine. If you ar 1. Install [Install WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10) and [Docker on Windows for WSL2](https://docs.microsoft.com/en-us/windows/wsl/tutorials/wsl-containers), or [Install Docker Linux](https://docs.docker.com/engine/install/ubuntu) (Docker-Compose is also required, and is intalled by default with Docker Desktop.) 1. [Install the Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli). Be sure to install the Azure CLI in your Linux or WSL environment. -> If you will be transferring this package to an air-gapped cloud, please run the pre-packaging requirements to build a package that's ready to be transferred. This will prepare a docker image with all requirements to run ezdeploy. This is necessary if you don't have access to an updated docker repo/pip repo in your target network. If you do have these, you can proceed with the installation as if installing to an internet connected Azure Cloud. +> If you will be transferring this package to an air-gapped cloud, please run the pre-packaging requirements to build a package that's ready to be transferred. This will prepare a docker image with all requirements to run ezdeploy. This is necessary if you don't have access to an updated docker repo/pip repo in your target network. If you do have these, you can proceed with the installation as if installing to an internet connected Azure Cloud. ## Step-By-Step -[Step-by-Step Remote Installation/Execution](#Step-by-Step-Azure-Installation) (recommended) +[Step-by-Step Remote Installation/Execution](#Step-by-Step-Azure-Installation) (recommended) [Step-by-Step Local Installation/Execution](#Step-by-Step-Local-Installation) (more difficult) To get started, you'll need to be running from a bash/zsh environment. If you are on a Windows machine you can use WSL2. @@ -27,49 +27,35 @@ This process will build the user interface container image on your workstation u Log in using the Azure CLI ```BASH -chmod u+x ./scripts/setup_ezdeploy.sh -./scripts/setup_ezdeploy.sh \ - -d build \ - -s \ - -t \ - -l \ - -e \ - -m \ - -p port \ - -0 \ - -1 \ - -2 \ - -3 -az login + az login ``` -> **Note:** For deployments to Azure Government, you will first need to set the cloud before logging in, such as: +Then deploy a container instance of the front end with: ```BASH - az cloud set --name AzureUSGovernment - az login +cd src/scripts +./setup_ezdeploy.sh -s ``` -From the "src" directory, set the access permissions to the `setup_ezdeploy.sh` deployment script and run it (the example below assumes the local workspace root directory is at `$HOME/missionlz`) - -```bash - cd $HOME/missionlz/src # -- your local workspace path may be different - - export TENANT_ID="" - export SUBSCRIPTION_ID="" - export TF_ENV="" - export MLZ_ENV="" - export LOCATION="" - - chmod u+x ./scripts/setup_ezdeploy.sh - - ./scripts/setup_ezdeploy.sh -d build -s $SUBSCRIPTION_ID -t $TENANT_ID -l $LOCATION -e $TF_ENV -m $MLZ_ENV -p 80 -0 $SUBSCRIPTION_ID -1 $SUBSCRIPTION_ID -2 $SUBSCRIPTION_ID -3 $SUBSCRIPTION_ID +`setup_ezdeploy.sh` has more configurable options, but these are the minimum required to deploy a running UI that will help you make a full MLZ deployment. + +Here's the full list of parameters for reference: + +```plaintext +setup_ezdeploy.sh: Setup the front end for MLZ + argument description + --docker-strategy -d [local|build|load] 'local' for localhost, 'build' to build from this repo, or 'load' to unzip an image + --subscription-id -s Subscription ID for MissionLZ resources + --location -l The location that you're deploying to (defaults to 'eastus') + --tf-environment -e Terraform azurerm environment (defaults to 'public') see: https://www.terraform.io/docs/language/settings/backends/azurerm.html#environment + --mlz-env-name -z Unique name for MLZ environment (defaults to 'mlz' + UNIX timestamp) + --port -p port to expose the front end web UI on (defaults to '80') + --hub-sub-id -h subscription ID for the hub network and resources (defaults to the value provided for -s --subscription-id) + --tier0-sub-id -0 subscription ID for tier 0 network and resources (defaults to the value provided for -s --subscription-id) + --tier1-sub-id -1 subscription ID for tier 1 network and resources (defaults to the value provided for -s --subscription-id) + --tier2-sub-id -2 subscription ID for tier 2 network and resources (defaults to the value provided for -s --subscription-id) ``` -> In the command above, the *<values-in-brackets>* need to be replaced with actual values from your environment - -The final results will include a URI that you can use to access the front end running in a remote azure container instance. - ### Step-by-Step Local Installation Running the user interface on your local workstation is not our recommended approach because it requires more setup, but it works. @@ -94,7 +80,7 @@ apt-get update \ Before running locally, you must follow the instructions in the primary readme file for this repo. You must have terraform pre-requisites installed in order to execute from a local system. Local execution will also require your credentials to have access to the service principal credentials for this system to assume; meaning that you should perform: -```BASH +```bash az login ``` @@ -102,70 +88,69 @@ prior to following the following instructions 1. Install and Source a Python Virtual Environment -```bash + ```bash python3 -m venv /path/to/new/virtual/environment source /path/to/new/virtual/environment/bin/activate -``` + ``` -2. Install requirements via pip +1. Install requirements via pip -```BASH - pip install -r src/front/requirements.txt -``` + ```bash + pip3 install -r src/front/requirements.txt + ``` -3. Run the installation scripts to deploy app requirements +1. Run the installation scripts to deploy app requirements You will need the following variables for the script: - subscription_id: is the subscription that will house all deployment artifacts: kv, storage, fe instance - - tenant_id: the tenant_id where all of your subscriptions are located - - tf_env_name: Please refer to [https://www.terraform.io/docs/language/settings/backends/azurerm.html#environment] for more information. (Defaults to Public) - - mlz_env_name: Can be anything unique to your deployment/environment it is used to ensure unique entries for resources. (Defaults to mlzdeployment) - - port: Default is 80, if you are running in WSL or otherwise can't bind to 80, use this flag to enter a port - - Multiple Subscriptions: - If you are running with multiple subscriptions, you'll need to use these flags with the setup command. - - -0: SACA Hub Subscription ID - -1: Tier 0 Subscription ID - -2: Tier 1 Subscription ID - -3: Tier 2 Subscription ID - -```bash - chmod u+x ./script/setup_ezdeploy.sh - ./script/setup_ezdeploy.sh -d local -s -t -l -e -m -p port p -0 -1 -2 -3 " -``` - -4. Invoke environment variables needed for login (These are returned after setup_ezdeploy.sh is run) - -```powershell - $env:CLIENT_ID="" - $env:CLIENT_SECRET=" -``` + `subscription_id`: is the subscription that will house all deployment artifacts: kv, storage, fe instance + + `port`: Default is 80, if you are running in WSL or otherwise can't bind to 80, use this flag to enter a port (e.g. 8081) + + ```bash + cd src/scripts + ./setup_ezdeploy.sh -d local -s -p + ``` + +1. Invoke environment variables needed for login (These are returned after setup_ezdeploy.sh is run) + + ```powershell + $env:CLIENT_ID='$client_id' + $env:CLIENT_SECRET='$client_password' + $env:TENANT_ID='$mlz_tenantid' + $env:MLZ_LOCATION='$mlz_config_location' + $env:SUBSCRIPTION_ID='$mlz_config_subid' + $env:HUB_SUBSCRIPTION_ID='HUB_SUBSCRIPTION_ID=$mlz_saca_subid' + $env:TIER0_SUBSCRIPTION_ID='TIER0_SUBSCRIPTION_ID=$mlz_tier0_subid' + $env:TIER1_SUBSCRIPTION_ID='TIER1_SUBSCRIPTION_ID=$mlz_tier1_subid' + $env:TIER2_SUBSCRIPTION_ID='TIER2_SUBSCRIPTION_ID=$mlz_tier2_subid' + $env:TF_ENV='$tf_environment' + $env:MLZ_ENV='$mlz_env_name' + $env:MLZCLIENTID='$(az keyvault secret show --name "${mlz_sp_kv_name}" --vault-name "${mlz_kv_name}" --query value --output tsv)' + $env:MLZCLIENTSECRET='$(az keyvault secret show --name "${mlz_sp_kv_password}" --vault-name "${mlz_kv_name}" --query value --output tsv)' + ``` + + ```bash + export CLIENT_ID=$auth_client_id + export CLIENT_SECRET=$auth_client_secret + export TENANT_ID=$mlz_tenantid + export MLZ_LOCATION=$mlz_config_location + export SUBSCRIPTION_ID=$mlz_config_subid + export HUB_SUBSCRIPTION_ID=$mlz_saca_subid + export TIER0_SUBSCRIPTION_ID=$mlz_tier0_subid + export TIER1_SUBSCRIPTION_ID=$mlz_tier1_subid + export TIER2_SUBSCRIPTION_ID=$mlz_tier2_subid + export TF_ENV=$tf_environment + export MLZ_ENV=$mlz_env_name + export MLZCLIENTID=$mlz_client_id + export MLZCLIENTSECRET=$mlz_client_secret + ``` + +1. Execute web server + + ```bash + cd src/front + python3 main.py + ``` You can then access the application by pointing your browser at "localhost". diff --git a/src/front/main.py b/src/front/main.py index 9f4f41b0e..f68f62d1d 100644 --- a/src/front/main.py +++ b/src/front/main.py @@ -27,7 +27,7 @@ keyVaultName = os.getenv("KEYVAULT_ID", None) if keyVaultName: - keyVaultUrl = "https://{}.vault.azure.net/".format(keyVaultName) + keyVaultUrl = "https://{}.vault.azure.net/".format(keyVaultName) # TODO (20210401): pass this by parameter or derive from cloud # This will use your Azure Managed Identity credential = DefaultAzureCredential() @@ -322,31 +322,15 @@ async def process_input(request: Request): for f_name, doc in tf_json.items(): json.dump(doc, open(os.path.join(os.getcwd(), "config_output", os.path.basename(f_name)), "w+")) - # Use what we know and write out the environment file: - with open(os.path.join(os.getcwd(), "config_output", "mlz_tf_var_front"), "w+") as f: - f.writelines("tf_environment=\"" + os.getenv("TF_ENV") + "\"\n" + - "mlz_env_name=\"" + os.getenv("MLZ_ENV") + "\"\n" + - "mlz_config_subid=\"" + os.getenv("SUBSCRIPTION_ID") + "\"\n" + - "mlz_config_location=\"" + os.getenv("MLZ_LOCATION") + "\"\n" + - "mlz_tenantid=\"" + os.getenv("TENANT_ID") + "\"\n" + - "mlz_tier0_subid=\"" + form_values["tier0_subid"] + "\"\n" + - "mlz_tier1_subid=\"" + form_values["tier1_subid"] + "\"\n" + - "mlz_tier2_subid=\"" + form_values["tier2_subid"] + "\"\n" + - "mlz_saca_subid=\"" + form_values["saca_subid"] + "\"\n") - - # Call the build script - # Check that it's executable: - config_executable = os.path.join(os.getcwd(), "..", "scripts", "mlz_tf_setup.sh") - apply_executable = os.path.join(os.getcwd(), "..", "build", "front_wrapper.sh") - os.chmod(config_executable, 0o755) - os.chmod(apply_executable, 0o755) - mlz_config = os.path.join(os.getcwd(), "config_output", "mlz_tf_var_front") - global_config = os.path.join(os.getcwd(), "config_output", "globals.tfvars.json") - saca = os.path.join(os.getcwd(), "config_output", "saca-hub.tfvars.json") - tier0 = os.path.join(os.getcwd(), "config_output", "tier-0.tfvars.json") - tier1 = os.path.join(os.getcwd(), "config_output", "tier-1.tfvars.json") - tier2 = os.path.join(os.getcwd(), "config_output", "tier-2.tfvars.json") + # set terraform vars paths + config_output_dir = os.path.join(os.getcwd(), "config_output") + global_vars = os.path.join(config_output_dir, "globals.tfvars.json") + saca_vars = os.path.join(config_output_dir, "saca-hub.tfvars.json") + tier0_vars = os.path.join(config_output_dir, "tier-0.tfvars.json") + tier1_vars = os.path.join(config_output_dir, "tier-1.tfvars.json") + tier2_vars = os.path.join(config_output_dir, "tier-2.tfvars.json") + # get service principal to execute terraform if keyVaultName: sp_id = secret_client.get_secret("serviceprincipal-clientid").value sp_pwd = secret_client.get_secret("serviceprincipal-pwd").value @@ -354,22 +338,53 @@ async def process_input(request: Request): sp_id = os.getenv("MLZCLIENTID", "NotSet") sp_pwd = os.getenv("MLZCLIENTSECRET", "NotSet") - config_str = "{} {} {}".format(config_executable, mlz_config, "bypass") - exec_str = "{} {} {} {} {} {} {} y {} {}".format( - apply_executable, mlz_config, global_config, saca, tier0, tier1, tier2, sp_id, sp_pwd) + # write a command to write mlz config: + src_dir = os.path.dirname(os.getcwd()) + generate_config_executable = os.path.join(src_dir, "scripts", "config", "generate_config_file.sh") + os.chmod(generate_config_executable, 0o755) + + mlz_config_path = os.path.join(src_dir, "mlz_tf_cfg.var") + + generate_config_args = [] + generate_config_args.append('--file ' + mlz_config_path) + generate_config_args.append('--tf-env ' + os.getenv("TF_ENV")) + generate_config_args.append('--metadatahost management.azure.com') # TODO (20210401): pass this by parameter or derive from cloud + generate_config_args.append('--mlz-env-name ' + os.getenv("MLZ_ENV")) + generate_config_args.append('--location ' + os.getenv("MLZ_LOCATION")) + generate_config_args.append('--config-sub-id ' + os.getenv("SUBSCRIPTION_ID")) + generate_config_args.append('--tenant-id ' + os.getenv("TENANT_ID")) + generate_config_args.append('--hub-sub-id ' + form_values["saca_subid"]) + generate_config_args.append('--tier0-sub-id ' + form_values["tier0_subid"]) + generate_config_args.append('--tier1-sub-id ' + form_values["tier1_subid"]) + generate_config_args.append('--tier2-sub-id ' + form_values["tier2_subid"]) + + generate_config_command = "{} {}".format(generate_config_executable, ' '.join(generate_config_args)) + + # write a command to execute front_wrapper.sh: + wrapper_executable = os.path.join(src_dir, "build", "front_wrapper.sh") + os.chmod(wrapper_executable, 0o755) + + wrapper_command = "{} {} {} {} {} {} {} y {} {}".format( + wrapper_executable, + mlz_config_path, + global_vars, + saca_vars, + tier0_vars, + tier1_vars, + tier2_vars, + sp_id, + sp_pwd) with open(exec_output, "w+") as out: - creation = await asyncio.create_subprocess_exec(*config_str.split(), stderr=out, stdout=out) + generate_mlz_config = await asyncio.create_subprocess_exec(*generate_config_command.split(), stderr=out, stdout=out) # This capture is setting to a dead object. If we want to do work with the process in the future # we have to do it here. - await creation.wait() - _ = await asyncio.create_subprocess_exec(*exec_str.split(), stderr=out, stdout=out) + await generate_mlz_config.wait() + _ = await asyncio.create_subprocess_exec(*wrapper_command.split(), stderr=out, stdout=out) return JSONResponse(content={"status": "success"}, status_code=200) - - # Execute a poll for the contents of a specific job, logs from terraform execution will be stored as text with # a job key ast he file name? @app.get("/poll") diff --git a/src/core/mlz_tf_cfg.var.sample b/src/mlz_tf_cfg.var.sample similarity index 83% rename from src/core/mlz_tf_cfg.var.sample rename to src/mlz_tf_cfg.var.sample index 76d403fdc..9092fb5e7 100644 --- a/src/core/mlz_tf_cfg.var.sample +++ b/src/mlz_tf_cfg.var.sample @@ -2,7 +2,7 @@ # Licensed under the MIT License. tf_environment="{TF_ENVIRONMENT}" # https://www.terraform.io/docs/language/settings/backends/azurerm.html#environment mlz_env_name="{MLZ_ENV_NAME}" # Unique name for MLZ environment -mlz_config_subid="{MLZ_CONFIG_SUBID}" # Subscription ID for Key Vault storing Service Principal creds +mlz_config_subid="{MLZ_CONFIG_SUBID}" # Subscription ID for MissionLZ configuration resources mlz_config_location="{MLZ_CONFIG_LOCATION}" # Azure Region for deploying Mission LZ configuration resources mlz_tenantid="{MLZ_TENANTID}" mlz_tier0_subid="{MLZ_TIER0_SUBID}" diff --git a/src/scripts/config/append_prereq_endpoints.sh b/src/scripts/config/append_prereq_endpoints.sh new file mode 100755 index 000000000..1c42330ca --- /dev/null +++ b/src/scripts/config/append_prereq_endpoints.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +# append pre-req endpoints to an MLZ config file + +set -e + +error_log() { + echo "${1}" 1>&2; +} + +usage() { + echo "append_prereq_endpoints.sh: append pre-req endpoints to an MLZ config file" + error_log "usage: append_prereq_endpoints.sh " +} + +if [[ "$#" -lt 1 ]]; then + usage + exit 1 +fi + +file_to_append=$1 + +cloudEndpoints=($(az cloud show \ + --query '[endpoints.resourceManager, suffixes.acrLoginServerEndpoint, suffixes.keyvaultDns]' \ + --output tsv)) + +resourceManager=${cloudEndpoints[0]} +acrLoginServerEndpoint=${cloudEndpoints[1]} +keyvaultDns=${cloudEndpoints[2]} + +append_if_not_empty() { + key_name=$1 + key_value=$2 + file=$3 + if [[ $key_value ]]; then + printf "${key_name}=${key_value}\n" >> "${file}" + fi +} + +append_if_not_empty "metadatahost" ${cloudEndpoints[0]} ${file_to_append} +append_if_not_empty "acrLoginServerEndpoint" ${cloudEndpoints[1]} ${file_to_append} +append_if_not_empty "keyvaultDns" ${cloudEndpoints[2]} ${file_to_append} diff --git a/src/scripts/config/generate_config_file.sh b/src/scripts/config/generate_config_file.sh new file mode 100755 index 000000000..1fd0edb85 --- /dev/null +++ b/src/scripts/config/generate_config_file.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +# Generate a configuration file for MLZ prerequisites and optional SACA and T0-T2 subscriptions. + +set -e + +error_log() { + echo "${1}" 1>&2; +} + +show_help() { + print_formatted() { + long_name=$1 + char_name=$2 + desc=$3 + printf "%15s %2s %s \n" "$long_name" "$char_name" "$desc" + } + print_formatted "argument" "" "description" + print_formatted "--file" "-f" "the destination file path and name (e.g. 'src/mlz_tf_cfg.var')" + print_formatted "--tf-env" "-e" "Terraform azurerm environment (e.g. 'public') see: https://www.terraform.io/docs/language/settings/backends/azurerm.html#environment" + print_formatted "--metadatahost" "-m" "Azure Metadata Service endpoint. (e.g 'management.azure.com' or 'management.usgovcloudapi.net')" + print_formatted "--mlz-env-name" "-z" "Unique name for MLZ environment" + print_formatted "--location" "-l" "The location that you're deploying to (e.g. 'eastus')" + print_formatted "--config-sub-id" "-s" "Subscription ID for MissionLZ configuration resources" + print_formatted "--tenant-id" "-t" "Tenant ID where your subscriptions live" + print_formatted "--hub-sub-id" "-h" "[OPTIONAL]: subscription ID for the hub network and resources" + print_formatted "--tier0-sub-id" "-0" "[OPTIONAL]: subscription ID for tier 0 network and resources" + print_formatted "--tier1-sub-id" "-1" "[OPTIONAL]: subscription ID for tier 1 network and resources" + print_formatted "--tier2-sub-id" "-2" "[OPTIONAL]: subscription ID for tier 2 network and resources" +} + +usage() { + echo "generate_config_file.sh: Generate a configuration file for MLZ prerequisites and optional SACA and T0-T2 subscriptions" + show_help +} + +# stage required parameters as not set +dest_file="notset" +tf_environment="notset" +metadatahost="notset" +mlz_env_name="notset" +mlz_config_location="notset" +mlz_config_subid="notset" +mlz_tenant_id="notset" + +# inspect arguments +while [ $# -gt 0 ] ; do + case $1 in + -f | --file) dest_file="$2" ;; + -e | --tf-env) tf_environment="$2" ;; + -m | --metadatahost) metadatahost="$2" ;; + -z | --mlz-env-name) mlz_env_name="$2" ;; + -l | --location) mlz_config_location="$2" ;; + -s | --config-sub-id) mlz_config_subid="$2" ;; + -t | --tenant-id) mlz_tenant_id="$2" ;; + -h | --hub-sub-id) mlz_saca_subid="$2" ;; + -0 | --tier0-sub-id) mlz_tier0_subid="$2" ;; + -1 | --tier1-sub-id) mlz_tier1_subid="$2" ;; + -2 | --tier2-sub-id) mlz_tier2_subid="$2" ;; + esac + shift +done + +# check mandatory parameters +for i in { $dest_file $tf_environment $metadatahost $mlz_env_name $mlz_config_location $mlz_config_subid $mlz_tenant_id } +do + if [[ $i == "notset" ]]; then + error_log "ERROR: Missing required arguments. These arguments are mandatory: -f, -e, -m, -z, -l, -s, -t" + usage + exit 1 + fi +done + +# write the file to the desired path +rm -f "$dest_file" +touch "$dest_file" +{ + echo "tf_environment=${tf_environment}" + echo "mlz_metadatahost=${metadatahost}" + echo "mlz_env_name=${mlz_env_name}" + echo "mlz_config_location=${mlz_config_location}" + echo "mlz_config_subid=${mlz_config_subid}" + echo "mlz_tenantid=${mlz_tenant_id}" +} >> "$dest_file" + +# for any optional parameters, check if they're set before appending them to the file +append_optional_args() { + key_name=$1 + key_value=$2 + default_value=$3 + file_to_append=$4 + if [[ $key_value ]]; then + printf "${key_name}=${key_value}\n" >> "${file_to_append}" + else + printf "${key_name}=${default_value}\n" >> "${file_to_append}" + fi +} +append_optional_args "mlz_saca_subid" "${mlz_saca_subid}" "${mlz_config_subid}" "${dest_file}" +append_optional_args "mlz_tier0_subid" "${mlz_tier0_subid}" "${mlz_config_subid}" "${dest_file}" +append_optional_args "mlz_tier1_subid" "${mlz_tier1_subid}" "${mlz_config_subid}" "${dest_file}" +append_optional_args "mlz_tier2_subid" "${mlz_tier2_subid}" "${mlz_config_subid}" "${dest_file}" + +# append cloud specific endpoints +this_script_path=$(realpath "${BASH_SOURCE%/*}") +. "${this_script_path}/append_prereq_endpoints.sh" ${dest_file} diff --git a/src/scripts/config/generate_names.sh b/src/scripts/config/generate_names.sh index 4ac7862a2..1a3653c46 100755 --- a/src/scripts/config/generate_names.sh +++ b/src/scripts/config/generate_names.sh @@ -26,15 +26,12 @@ if [[ "$#" -lt 1 ]]; then exit 1 fi -# Front End By Pass Check -if [[ ${1} != "bypass" ]]; then - mlz_config=$(realpath "${1}") - tf_sub_id_raw=${2:-notset} - tf_name_raw=${3:-notset} +mlz_config=$(realpath "${1}") +tf_sub_id_raw=${2:-notset} +tf_name_raw=${3:-notset} - # source variables from MLZ config - . "${mlz_config}" -fi +# source variables from MLZ config +. "${mlz_config}" # remove hyphens for resource naming restrictions # in the future, do more cleansing @@ -68,8 +65,6 @@ export mlz_fe_app_name="${mlz_fe_app_name_full:0:24}" export mlz_instance_name="${mlz_instance_name_full:0:24}" export mlz_dns_name="${mlz_dns_name_full:0:24}" -# FE Resources - if [[ $tf_name_raw != "notset" ]]; then # remove hyphens for resource naming restrictions # in the future, do more cleansing diff --git a/src/scripts/config/mlz_config_create.sh b/src/scripts/config/mlz_config_create.sh index eb50f8fbf..eb7a28e64 100755 --- a/src/scripts/config/mlz_config_create.sh +++ b/src/scripts/config/mlz_config_create.sh @@ -65,25 +65,19 @@ if [[ "$#" -lt 1 ]]; then exit 1 fi -# Front End By Pass Check -if [[ ${1} != "bypass" ]]; then - - mlz_tf_cfg=$(realpath "${1}") - - # Source variables - . "${mlz_tf_cfg}" - - # Create array of unique subscription IDs. The 'sed' command below search thru the source - # variables file looking for all lines that do not have a '#' in the line. If a line with - # a '#' is found, the '#' and ever character after it in the line is ignored. The output - # of what remains from the sed command is then piped to grep to find the words that match - # the pattern. These words are what make up the 'mlz_subs' array. - mlz_sub_pattern="mlz_.*._subid" - mlz_subs=$(< "${mlz_tf_cfg}" sed 's:#.*$::g' | grep -w "${mlz_sub_pattern}") - subs=() -else - mlz_tf_cfg="bypass" -fi +mlz_tf_cfg=$(realpath "${1}") + +# Source variables +. "${mlz_tf_cfg}" + +# Create array of unique subscription IDs. The 'sed' command below search thru the source +# variables file looking for all lines that do not have a '#' in the line. If a line with +# a '#' is found, the '#' and ever character after it in the line is ignored. The output +# of what remains from the sed command is then piped to grep to find the words that match +# the pattern. These words are what make up the 'mlz_subs' array. +mlz_sub_pattern="mlz_.*._subid" +mlz_subs=$(< "${mlz_tf_cfg}" sed 's:#.*$::g' | grep -w "${mlz_sub_pattern}") +subs=() # generate MLZ configuration names . "${BASH_SOURCE%/*}/generate_names.sh" "${mlz_tf_cfg}" @@ -131,9 +125,6 @@ if [[ -z $(az ad sp list --filter "displayName eq '${mlz_sp_name}'" --query "[]. --query objectId \ --output tsv) - # Make available to calling scripts - export sp_objid=${sp_objid} - # Assign Contributor role to Service Principal for sub in "${subs[@]}" do diff --git a/src/scripts/container-registry/add_auth_scopes.sh b/src/scripts/container-registry/add_auth_scopes.sh new file mode 100755 index 000000000..9c18347e4 --- /dev/null +++ b/src/scripts/container-registry/add_auth_scopes.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +# shellcheck disable=SC1090,2154 +# SC1090: Can't follow non-constant source. Use a directive to specify location. +# SC2154: "var is referenced but not assigned". These values come from an external file. +# +# create an app registration and add MSAL auth scopes to facilitate user logon to MLZ UI + +set -e + +error_log() { + echo "${1}" 1>&2; +} + +usage() { + echo "add_auth_scopes.sh: create an app registration and add MSAL auth scopes to facilitate user logon to MLZ UI" + error_log "usage: add_auth_scopes.sh " +} + +if [[ "$#" -lt 2 ]]; then + usage + exit 1 +fi + +mlz_config_file=$1 +fqdn=$2 + +# generate MLZ configuration names +. "${mlz_config_file}" +. "$(dirname "$(realpath "${BASH_SOURCE%/*}")")/config/generate_names.sh" "${mlz_config_file}" + +# path to app resources definition file +required_resources_json_file="$(dirname "$(realpath "${BASH_SOURCE%/*}")")/config/mlz_login_app_resources.json" + +# generate app registration +echo "INFO: creating app registration ${mlz_fe_app_name} to facilitate user logon at ${fqdn}..." +client_id=$(az ad app create \ + --display-name "${mlz_fe_app_name}" \ + --reply-urls "http://${fqdn}/redirect" \ + --required-resource-accesses "${required_resources_json_file}" \ + --query appId \ + --output tsv) +client_password=$(az ad app credential reset \ + --id ${client_id} \ + --query password \ + --only-show-errors \ + --output tsv) + +# update keyvault with the app registration information +echo "INFO: storing app registration information for client ID ${client_id} in ${mlz_kv_name}..." +az keyvault secret set \ + --name "${mlz_login_app_kv_name}" \ + --subscription "${mlz_config_subid}" \ + --vault-name "${mlz_kv_name}" \ + --value "${client_id}" \ + --only-show-errors \ + --output none + +az keyvault secret set \ + --name "${mlz_login_app_kv_password}" \ + --subscription "${mlz_config_subid}" \ + --vault-name "${mlz_kv_name}" \ + --value "${client_password}" \ + --only-show-errors \ + --output none + +echo "INFO: waiting thirty seconds to allow for app registration propogation..." +sleep 30 diff --git a/src/scripts/container-registry/create_acr.sh b/src/scripts/container-registry/create_acr.sh new file mode 100755 index 000000000..664d8a5c4 --- /dev/null +++ b/src/scripts/container-registry/create_acr.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +# shellcheck disable=SC1090,SC2154 +# SC1090: Can't follow non-constant source. Use a directive to specify location. +# SC2154: "var is referenced but not assigned". These values come from an external file. +# +# create an Azure Container Registry for hosting the MLZ UI given an MLZ configuration + +set -e + +error_log() { + echo "${1}" 1>&2; +} + +usage() { + echo "create_acr.sh: create an Azure Container Registry for hosting the MLZ UI given an MLZ configuration" + error_log "usage: create_acr.sh " +} + +if [[ "$#" -lt 1 ]]; then + usage + exit 1 +fi + +mlz_config_file=$1 + +# generate MLZ configuration names +. "${mlz_config_file}" +. "$(dirname "$(realpath "${BASH_SOURCE%/*}")")/config/generate_names.sh" "${mlz_config_file}" + +echo "INFO: creating Azure Container Registry ${mlz_acr_name}..." +az acr create \ + --resource-group "${mlz_rg_name}" \ + --name "${mlz_acr_name}" \ + --sku Basic \ + --only-show-errors \ + --output none + +echo "INFO: enabling administration of registry ${mlz_acr_name}..." +sleep 60 +az acr update \ + --name "${mlz_acr_name}" \ + --admin-enabled true \ + --only-show-errors \ + --output none + +az acr login \ + --name "${mlz_acr_name}" \ + --only-show-errors \ + --output none + +acr_id=$(az acr show \ + --name "${mlz_acr_name}" \ + --query id \ + --output tsv) + +client_id=$(az keyvault secret show \ + --name "${mlz_sp_kv_name}" \ + --vault-name "${mlz_kv_name}" \ + --query value \ + --only-show-errors \ + --output tsv) + +echo "INFO: granting registry identity ${client_id} 'acrpull' on ${mlz_acr_name}..." + +az role assignment create \ + --assignee "${client_id}" \ + --scope "${acr_id}" \ + --role acrpull \ + --only-show-errors \ + --output none diff --git a/src/scripts/container-registry/deploy_instance.sh b/src/scripts/container-registry/deploy_instance.sh new file mode 100755 index 000000000..78d5b39d2 --- /dev/null +++ b/src/scripts/container-registry/deploy_instance.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +# shellcheck disable=SC1090,SC2154 +# SC1090: Can't follow non-constant source. Use a directive to specify location. +# SC2154: "var is referenced but not assigned". These values come from an external file. +# +# deploy a docker image to Azure Container Registry that hosts the MLZ UI + +set -e + +error_log() { + echo "${1}" 1>&2; +} + +usage() { + echo "deploy_instance.sh: deploy a docker image to Azure Container Registry that hosts the MLZ UI" + error_log "usage: deploy_instance.sh " +} + +if [[ "$#" -lt 3 ]]; then + usage + exit 1 +fi + +mlz_config_file=$1 +image_name=$2 +image_tag=$3 + +# generate MLZ configuration names +. "${mlz_config_file}" +. "$(dirname "$(realpath "${BASH_SOURCE%/*}")")/config/generate_names.sh" "${mlz_config_file}" + +acr_login_server=$(az acr show \ + --name "${mlz_acr_name}" \ + --resource-group "${mlz_rg_name}" \ + --query "loginServer" \ + --output tsv) + +echo "INFO: creating instance of ${image_name}:${image_tag} on ${mlz_instance_name} in ${mlz_acr_name}..." + +registry_username=$(az keyvault secret show \ + --name "${mlz_sp_kv_name}" \ + --vault-name "${mlz_kv_name}" \ + --query value \ + --output tsv) + +registry_password=$(az keyvault secret show \ + --name "${mlz_sp_kv_password}" \ + --vault-name "${mlz_kv_name}" \ + --query value \ + --output tsv) + +# set container environment variables from MLZ config +env_vars_args=() +env_vars_args+=("KEYVAULT_ID=${mlz_kv_name}") +env_vars_args+=("TENANT_ID=${mlz_tenantid}") +env_vars_args+=("MLZ_LOCATION=${mlz_config_location}") +env_vars_args+=("SUBSCRIPTION_ID=${mlz_config_subid}") +env_vars_args+=("TF_ENV=${tf_environment}") +env_vars_args+=("MLZ_ENV=${mlz_env_name}") +env_vars_args+=("HUB_SUBSCRIPTION_ID=${mlz_saca_subid}") +env_vars_args+=("TIER0_SUBSCRIPTION_ID=${mlz_tier0_subid}") +env_vars_args+=("TIER1_SUBSCRIPTION_ID=${mlz_tier1_subid}") +env_vars_args+=("TIER2_SUBSCRIPTION_ID=${mlz_tier2_subid}") + +# expand array into a string of space separated arguments +env_vars=$(printf '%s ' "${env_vars_args[*]}") + +# do not quote args $env_vars, we intend to split +# ignoring shellcheck for word splitting because that is the desired behavior +# shellcheck disable=SC2086 +az container create \ + --resource-group "${mlz_rg_name}" \ + --name "${mlz_instance_name}" \ + --image "${acr_login_server}/${image_name}:${image_tag}" \ + --dns-name-label "${mlz_dns_name}" \ + --environment-variables $env_vars \ + --registry-username "${registry_username}" \ + --registry-password "${registry_password}" \ + --ports 80 \ + --assign-identity \ + --only-show-errors \ + --output none + +echo "INFO: granting instance ${mlz_instance_name} necessary permissions to keyvault ${mlz_kv_name}..." + +container_obj_id=$(az container show \ + --resource-group "${mlz_rg_name}" \ + --name "${mlz_instance_name}" \ + --query identity.principalId \ + --output tsv) + +az keyvault set-policy \ + --name "${mlz_kv_name}" \ + --key-permissions get list \ + --secret-permissions get list \ + --object-id "${container_obj_id}" \ + --only-show-errors \ + --output none diff --git a/src/scripts/ezdeploy_docker.sh b/src/scripts/ezdeploy_docker.sh deleted file mode 100755 index 3b1e52957..000000000 --- a/src/scripts/ezdeploy_docker.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -# -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# -# shellcheck disable=SC1090,SC1091 -# SC1090: Can't follow non-constant source. Use a directive to specify location. -# SC1091: Not following. Shellcheck can't follow non-constant source. -# -# This script builds and tags the docker image - -set -e - -error_log() { - echo "${1}" 1>&2; -} - -# Check for Docker CLI -if ! command -v docker &> /dev/null; then - echo "Docker could not be found. Docker is required to build docker images." - echo "see https://docs.docker.com/engine/install/ubuntu for installation instructions." - exit 1 -fi - -usage() { - echo "ezdeploy_docker.sh: If using 'load' will load from a specified zip file, if using 'build' will build the docker image from the dockerfile." - error_log "usage: ezdeploy_docker.sh {{default=build}}" -} - -if [[ "$#" -lt 1 ]]; then - usage - exit 1 -fi - -echo "INFO: building docker container" - if [[ "${1}" == "build" ]]; then - docker build -t lzfront "${BASH_SOURCE%/*}/../" - elif [[ "${1}" == "load" ]]; then - #TODO: Change this to a file pointer - unzip mlz.zip . - docker load -i mlz.tar - else - echo "Unrecognized docker strategy detected. Must be build or load" - fi - diff --git a/src/scripts/mlz_tf_setup.sh b/src/scripts/mlz_tf_setup.sh index 91b3fd6fb..b431f732b 100755 --- a/src/scripts/mlz_tf_setup.sh +++ b/src/scripts/mlz_tf_setup.sh @@ -17,8 +17,8 @@ error_log() { } usage() { - echo "mlz_tf_setup.sh: configure a resource group that contains Terraform state and a secret store, the presence of bypass indicates skipping primary creation" - error_log "usage: mlz_tf_setup.sh " + echo "mlz_tf_setup.sh: configure a resource group that contains Terraform state and a secret store" + error_log "usage: mlz_tf_setup.sh " } if [[ "$#" -lt 1 ]]; then @@ -49,9 +49,7 @@ create_tf_config() { ################################################## # generate MLZ configuration resources -if [[ ${2} != "bypass" ]]; then - . "${BASH_SOURCE%/*}/config/mlz_config_create.sh" "${mlz_tf_cfg}" "${mlz_env_name}" "${mlz_config_location}" -fi +. "${BASH_SOURCE%/*}/config/mlz_config_create.sh" "${mlz_tf_cfg}" "${mlz_env_name}" "${mlz_config_location}" create_tf_config "${mlz_saca_subid}" "${core_path}/saca-hub" create_tf_config "${mlz_tier0_subid}" "${core_path}/tier-0" diff --git a/src/scripts/setup_ezdeploy.sh b/src/scripts/setup_ezdeploy.sh index 9e8d0cd6a..3884ce964 100755 --- a/src/scripts/setup_ezdeploy.sh +++ b/src/scripts/setup_ezdeploy.sh @@ -3,7 +3,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # -# shellcheck disable=SC1090,SC1091,2154 +# shellcheck disable=SC1083,SC1090,SC1091,2154 +# SC1083: This is literal. # SC1090: Can't follow non-constant source. Use a directive to specify location. # SC1091: Not following. Shellcheck can't follow non-constant source. # SC2154: "var is referenced but not assigned". These values come from an external file. @@ -16,197 +17,169 @@ error_log() { echo "${1}" 1>&2; } +show_help() { + print_formatted() { + long_name=$1 + char_name=$2 + desc=$3 + printf "%20s %2s %s \n" "$long_name" "$char_name" "$desc" + } + print_formatted "argument" "" "description" + print_formatted "--docker-strategy" "-d" "[local|build|load] 'local' for localhost, 'build' to build from this repo, or 'load' to unzip an image (defaults to 'build')" + print_formatted "--subscription-id" "-s" "Subscription ID for MissionLZ resources" + print_formatted "--location" "-l" "The location that you're deploying to (defaults to 'eastus')" + print_formatted "--tf-environment" "-e" "Terraform azurerm environment (defaults to 'public') see: https://www.terraform.io/docs/language/settings/backends/azurerm.html#environment" + print_formatted "--mlz-env-name" "-z" "Unique name for MLZ environment (defaults to 'mlz' + UNIX timestamp)" + print_formatted "--hub-sub-id" "-h" "subscription ID for the hub network and resources (defaults to the value provided for -s --subscription-id)" + print_formatted "--tier0-sub-id" "-0" "subscription ID for tier 0 network and resources (defaults to the value provided for -s --subscription-id)" + print_formatted "--tier1-sub-id" "-1" "subscription ID for tier 1 network and resources (defaults to the value provided for -s --subscription-id)" + print_formatted "--tier2-sub-id" "-2" "subscription ID for tier 2 network and resources (defaults to the value provided for -s --subscription-id)" +} + usage() { - echo "setup_ezdeploy.sh: Setup the Front End for MLZ" - error_log "usage: setup_ezdeploy.sh -d -s -t -l -e -m -p -0 -1 -2 -3 " + echo "setup_ezdeploy.sh: Setup the front end for MLZ" + show_help } -if [[ "$#" -lt 8 ]]; then - usage - exit 1 +timestamp=$(date +%s) + +metadata_host="management.azure.com" # TODO (20210401): pass this by parameter or derive from cloud +acr_endpoint="azurecr.io" # TODO (20210401): pass this by parameter or derive from cloud + +# set helpful defaults that can be overridden or 'notset' for mandatory input +docker_strategy="build" +mlz_config_subid="notset" +mlz_config_location="eastus" +tf_environment="public" +mlz_env_name="mlz${timestamp}" +web_port="80" +subs_args=() + +# inspect user input +while [ $# -gt 0 ] ; do + case $1 in + -d | --docker-strategy) docker_strategy="$2" ;; + -s | --subscription-id) mlz_config_subid="$2" ;; + -l | --location) mlz_config_location="$2" ;; + -e | --tf-environment) tf_environment="$2" ;; + -z | --mlz-env-name) mlz_env_name="$2" ;; + -p | --port) web_port="$2" ;; + -h | --hub-sub-id) subs_args+=("-h ${2}") ;; + -0 | --tier0-sub-id) subs_args+=("-0 ${2}") ;; + -1 | --tier1-sub-id) subs_args+=("-1 ${2}") ;; + -2 | --tier2-sub-id) subs_args+=("-2 ${2}") ;; + esac + shift +done + +# setup paths +this_script_path=$(realpath "${BASH_SOURCE%/*}") +src_path=$(dirname "${this_script_path}") +container_registry_path="$(realpath "${this_script_path}")/container-registry" + +# check for dependencies +"${this_script_path}/util/checkforazcli.sh" +"${this_script_path}/util/checkfordocker.sh" + +# check mandatory parameters +for i in { $docker_strategy $mlz_config_subid $mlz_config_location $tf_environment $mlz_env_name $web_port } +do + if [[ $i == "notset" ]]; then + error_log "ERROR: Missing required arguments. These arguments are mandatory: -d, -s, -l, -e, -z, -p" + usage + exit 1 + fi +done + +# check docker strategy +if [[ $docker_strategy != "local" && \ + $docker_strategy != "build" && \ + $docker_strategy != "load" ]]; then + error_log "ERROR: Unrecognized docker strategy detected. Must be 'local', 'build', or 'load'." + exit 1 fi -export tf_environment=public -export mlz_env_name=mlzdeployment -export web_port=80 +# switch to the MLZ subscription +echo "INFO: setting current az cli subscription to ${mlz_config_subid}..." +az account set --subscription "${mlz_config_subid}" -subs=() -add_unique_sub_to_array() { - if [[ ! "${subs[*]}" =~ ${1} ]];then - subs+=("${1}") - fi -} +# retrieve tenant ID for the MLZ subscription +mlz_tenantid=$(az account show \ + --query "tenantId" \ + --output tsv) -while getopts "d:s:t:l:e:m:p:0:1:2:3:4:" opts; do - case "${opts}" in - d) export docker_strategy=${OPTARG} - ;; - s) export mlz_config_subid=${OPTARG} - add_unique_sub_to_array "${OPTARG}" - ;; - t) export mlz_tenantid=${OPTARG} - ;; - l) export mlz_config_location=${OPTARG} - ;; - e) export tf_environment=${OPTARG} - ;; - m) export mlz_env_name=${OPTARG} - ;; - p) export web_port=${OPTARG} - ;; - 0) export mlz_saca_subid=${OPTARG} - add_unique_sub_to_array "${OPTARG}" - ;; - 1) export mlz_tier0_subid=${OPTARG} - add_unique_sub_to_array "${OPTARG}" - ;; - 2) export mlz_tier1_subid=${OPTARG} - add_unique_sub_to_array "${OPTARG}" - ;; - 3) export mlz_tier2_subid=${OPTARG} - add_unique_sub_to_array "${OPTARG}" - ;; - ?) - echo "Invalid option: -${OPTARG}." - exit 2 - ;; - esac +# create MLZ configuration file based on user input +mlz_config_file="${src_path}/mlz_tf_cfg.var" +echo "INFO: creating a MLZ config file based on user input at $(realpath "$mlz_config_file")..." + +### derive args from user input +gen_config_args=() +gen_config_args+=("-f ${mlz_config_file}") +gen_config_args+=("-e ${tf_environment}") +gen_config_args+=("-m ${metadata_host}") +gen_config_args+=("-z ${mlz_env_name}") +gen_config_args+=("-l ${mlz_config_location}") +gen_config_args+=("-s ${mlz_config_subid}") +gen_config_args+=("-t ${mlz_tenantid}") + +### add hubs and spokes, if present +for j in "${subs_args[@]}" +do + gen_config_args+=("$j") done +### expand array into a string of space separated arguments +gen_config_args_str=$(printf '%s ' "${gen_config_args[*]}") + +### create the file +### do not quote args $gen_config_args_str, we intend to split +### ignoring shellcheck for word splitting because that is the desired behavior +# shellcheck disable=SC2086 +"${this_script_path}/config/generate_config_file.sh" $gen_config_args_str + # generate MLZ configuration names -. "${BASH_SOURCE%/*}/config/generate_names.sh" "bypass" - -# create the subscription resources -. "${BASH_SOURCE%/*}/config/mlz_config_create.sh" "bypass" - -for sub in "${subs[@]}" - do - echo "Setting Contributor role assignment for ${mlz_sp_name} on subscription ID: ${sub}" - az role assignment create \ - --role Contributor \ - --assignee-object-id "${sp_objid}" \ - --scope "/subscriptions/${sub}" \ - --assignee-principal-type ServicePrincipal \ - --output none - done - -echo "INFO: Setting current az cli subscription to ${mlz_config_subid}" -az account set --subscription "${mlz_config_subid}" +. "${this_script_path}/config/generate_names.sh" "$mlz_config_file" -# Handle Deployment of Login Services +# create MLZ required resources +echo "INFO: setting up required MLZ resources using $(realpath "$mlz_config_file")..." +"${this_script_path}/config/mlz_config_create.sh" "$mlz_config_file" -# Handle Remote Deploy to a Container Instance -if [[ $docker_strategy != "local" ]]; then - echo "Creating ACR" - az acr create \ - --resource-group "${mlz_rg_name}" \ - --name "${mlz_acr_name}" \ - --sku Basic +# if local, call setup_ezdeploy_local +if [[ $docker_strategy == "local" ]]; then + "${this_script_path}/setup_ezdeploy_local.sh" "$mlz_config_file" "$web_port" + exit 0 +fi - echo "Waiting for registry completion and running post process to enable admin on ACR" - sleep 60 - az acr update --name "${mlz_acr_name}" --admin-enabled true +# otherwise, create container registry +"${container_registry_path}/create_acr.sh" "$mlz_config_file" - . "${BASH_SOURCE%/*}/ezdeploy_docker.sh" "$docker_strategy" +# build/load, tag, and push image +image_name="lzfront" +image_tag="latest" - docker tag lzfront:latest "${mlz_acr_name}.azurecr.io/lzfront:latest" +if [[ $docker_strategy == "build" ]]; then + docker build -t "${image_name}" "${src_path}" +fi - echo "INFO: Logging into Container Registry" - az acr login --name "${mlz_acr_name}" +if [[ $docker_strategy == "load" ]]; then + unzip mlz.zip . + docker load -i mlz.tar +fi - ACR_REGISTRY_ID=$(az acr show --name "${mlz_acr_name}" --query id --output tsv) - az role assignment create --assignee "$(az keyvault secret show --name "${mlz_sp_kv_name}" --vault-name "${mlz_kv_name}" --query value --output tsv)" --scope "${ACR_REGISTRY_ID}" --role acrpull +docker tag "${image_name}:${image_tag}" "${mlz_acr_name}.${acr_endpoint}/${image_name}:${image_tag}" +docker push "${mlz_acr_name}.${acr_endpoint}/${image_name}:${image_tag}" - echo "INFO: pushing docker container" - docker tag lzfront:latest "${mlz_acr_name}".azurecr.io/lzfront:latest - docker push "${mlz_acr_name}".azurecr.io/lzfront:latest - ACR_LOGIN_SERVER=$(az acr show --name "${mlz_acr_name}" --resource-group "${mlz_rg_name}" --query "loginServer" --output tsv) +# deploy an instance +"${container_registry_path}/deploy_instance.sh" "$mlz_config_file" "$image_name" "$image_tag" - echo "INFO: creating instance" - fqdn=$(az container create \ +# get the URL for the instance +container_fqdn=$(az container show \ --resource-group "${mlz_rg_name}"\ --name "${mlz_instance_name}" \ - --image "$ACR_LOGIN_SERVER"/lzfront:latest \ - --dns-name-label "${mlz_dns_name}" \ - --environment-variables KEYVAULT_ID="${mlz_kv_name}" TENANT_ID="${mlz_tenantid}" MLZ_LOCATION="${mlz_config_location}" SUBSCRIPTION_ID="${mlz_config_subid}" TF_ENV="${tf_environment}" MLZ_ENV="${mlz_env_name}" \ - --registry-username "$(az keyvault secret show --name "${mlz_sp_kv_name}" --vault-name "${mlz_kv_name}" --query value --output tsv)" \ - --registry-password "$(az keyvault secret show --name "${mlz_sp_kv_password}" --vault-name "${mlz_kv_name}" --query value --output tsv)" \ - --ports 80 \ --query ipAddress.fqdn \ - --assign-identity \ --output tsv) - echo "INFO: Giving Instance the necessary permissions" - az keyvault set-policy \ - -n "${mlz_kv_name}" \ - --key-permissions get list \ - --secret-permissions get list \ - --object-id "$(az container show --resource-group "${mlz_rg_name}" --name "${mlz_instance_name}" --query identity.principalId --output tsv)" -else - fqdn="localhost" -fi - -if [[ $web_port != 80 ]]; then - fqdn+=":$web_port" -fi +# create an app registration and add auth scopes to facilitate MSAL login for the instance +"${container_registry_path}/add_auth_scopes.sh" "$mlz_config_file" "$container_fqdn" - -# Generate the Login EndPoint for Security Purposes -echo "Creating App Registration to facilitate login capabilities" -client_id=$(az ad app create \ - --display-name "${mlz_fe_app_name}" \ - --reply-urls "http://$fqdn/redirect" \ - --required-resource-accesses "${BASH_SOURCE%/*}"/config/mlz_login_app_resources.json \ - --query appId \ - --output tsv) - -client_password=$(az ad app credential reset \ - --id "$client_id" \ - --query password \ - --output tsv) - -echo "Storing client id at ${mlz_login_app_kv_name}" -az keyvault secret set \ - --name "${mlz_login_app_kv_name}" \ - --subscription "${mlz_config_subid}" \ - --vault-name "${mlz_kv_name}" \ - --value "$client_id" \ - --output none - -echo "Storing client secret at ${mlz_login_app_kv_password}" -az keyvault secret set \ - --name "${mlz_login_app_kv_password}" \ - --subscription "${mlz_config_subid}" \ - --vault-name "${mlz_kv_name}" \ - --value "$client_password" \ - --output none - -echo "KeyVault updated with Login App Registration secret!" -echo "All steps have been completed you will need the following to access the configuration utility:" - -if [[ $docker_strategy == "local" ]]; then - echo "Your environment variables for local execution are:" - echo "Copy-Paste:" - echo "Bash:" - echo "export CLIENT_ID=$client_id" - echo "export CLIENT_SECRET=$client_password" - echo "export TENANT_ID=$mlz_tenantid" - echo "export LOCATION=$mlz_config_location" - echo "export SUBSCRIPTION_ID=$mlz_config_subid" - echo "export TF_ENV=$tf_environment" - echo "export MLZ_ENV=$mlz_env_name" - echo "export MLZCLIENTID=$(az keyvault secret show --name "${mlz_sp_kv_name}" --vault-name "${mlz_kv_name}" --query value --output tsv)" - echo "export MLZCLIENTSECRET=$(az keyvault secret show --name "${mlz_sp_kv_password}" --vault-name "${mlz_kv_name}" --query value --output tsv)" - echo "Powershell:" - echo "\$env:CLIENT_ID='$client_id'" - echo "\$env:CLIENT_SECRET='$client_password'" - echo "\$env:TENANT_ID='$mlz_tenantid'" - echo "\$env:LOCATION='$mlz_config_location'" - echo "\$env:SUBSCRIPTION_ID='$mlz_config_subid'" - echo "\$env:TF_ENV='$tf_environment'" - echo "\$env:MLZ_ENV='$mlz_env_name'" - echo "\$env:MLZCLIENTID='$(az keyvault secret show --name "${mlz_sp_kv_name}" --vault-name "${mlz_kv_name}" --query value --output tsv)'" - echo "\$env:MLZCLIENTSECRET='$(az keyvault secret show --name "${mlz_sp_kv_password}" --vault-name "${mlz_kv_name}" --query value --output tsv)'" -else - echo "You can access the front end at http://$fqdn" -fi \ No newline at end of file +echo "INFO: COMPLETE! You can access the front end at http://$container_fqdn" diff --git a/src/scripts/setup_ezdeploy_local.sh b/src/scripts/setup_ezdeploy_local.sh new file mode 100755 index 000000000..1395baae4 --- /dev/null +++ b/src/scripts/setup_ezdeploy_local.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +# shellcheck disable=SC1090,2154 +# SC1090: Can't follow non-constant source. Use a directive to specify location. +# SC2154: "var is referenced but not assigned". These values come from an external file. +# +# setup a local front end for MLZ + +set -e + +error_log() { + echo "${1}" 1>&2; +} + +usage() { + echo "setup_ezdeploy_local.sh: setup a local front end for MLZ" + error_log "usage: setup_ezdeploy.sh " +} + +if [[ "$#" -lt 2 ]]; then + usage + exit 1 +fi + +mlz_config_file=$1 +web_port=$2 + +container_registry_path="$(realpath "${BASH_SOURCE%/*}")/container-registry" + +# source mlz_config_file +. "${mlz_config_file}" + +# generate MLZ configuration names +. "$(realpath "${BASH_SOURCE%/*}")/config/generate_names.sh" "$mlz_config_file" + +# create auth scopes +local_fqdn="localhost:${web_port}" +"${container_registry_path}/add_auth_scopes.sh" "$mlz_config_file" "$local_fqdn" + +auth_client_id=$(az keyvault secret show \ + --name "${mlz_login_app_kv_name}" \ + --vault-name "${mlz_kv_name}" \ + --query value \ + --output tsv) +auth_client_secret=$(az keyvault secret show \ + --name "${mlz_login_app_kv_password}" \ + --vault-name "${mlz_kv_name}" \ + --query value \ + --output tsv) +mlz_client_id=$(az keyvault secret show \ + --name "${mlz_sp_kv_name}" \ + --vault-name "${mlz_kv_name}" \ + --query value \ + --output tsv) +mlz_client_secret=$(az keyvault secret show \ + --name "${mlz_sp_kv_password}" \ + --vault-name "${mlz_kv_name}" \ + --query value \ + --output tsv) + +# echo out env vars +echo "INFO: Complete!" +echo "==============================" +echo "INFO: 1) To run the UI first set the environment variables below" +echo "==============================" + +echo "for bash:" +echo "export CLIENT_ID=$auth_client_id" +echo "export CLIENT_SECRET=$auth_client_secret" +echo "export TENANT_ID=$mlz_tenantid" +echo "export MLZ_LOCATION=$mlz_config_location" +echo "export SUBSCRIPTION_ID=$mlz_config_subid" +echo "export HUB_SUBSCRIPTION_ID=$mlz_saca_subid" +echo "export TIER0_SUBSCRIPTION_ID=$mlz_tier0_subid" +echo "export TIER1_SUBSCRIPTION_ID=$mlz_tier1_subid" +echo "export TIER2_SUBSCRIPTION_ID=$mlz_tier2_subid" +echo "export TF_ENV=$tf_environment" +echo "export MLZ_ENV=$mlz_env_name" +echo "export MLZCLIENTID=$mlz_client_id" +echo "export MLZCLIENTSECRET=$mlz_client_secret" + +echo "for PowerShell:" +echo "\$env:CLIENT_ID='$client_id'" +echo "\$env:CLIENT_SECRET='$client_password'" +echo "\$env:TENANT_ID='$mlz_tenantid'" +echo "\$env:MLZ_LOCATION='$mlz_config_location'" +echo "\$env:SUBSCRIPTION_ID='$mlz_config_subid'" +echo "\$env:HUB_SUBSCRIPTION_ID='HUB_SUBSCRIPTION_ID=$mlz_saca_subid'" +echo "\$env:TIER0_SUBSCRIPTION_ID='TIER0_SUBSCRIPTION_ID=$mlz_tier0_subid'" +echo "\$env:TIER1_SUBSCRIPTION_ID='TIER1_SUBSCRIPTION_ID=$mlz_tier1_subid'" +echo "\$env:TIER2_SUBSCRIPTION_ID='TIER2_SUBSCRIPTION_ID=$mlz_tier2_subid'" +echo "\$env:TF_ENV='$tf_environment'" +echo "\$env:MLZ_ENV='$mlz_env_name'" +echo "\$env:MLZCLIENTID='$(az keyvault secret show --name "${mlz_sp_kv_name}" --vault-name "${mlz_kv_name}" --query value --output tsv)'" +echo "\$env:MLZCLIENTSECRET='$(az keyvault secret show --name "${mlz_sp_kv_password}" --vault-name "${mlz_kv_name}" --query value --output tsv)'" + +echo "==============================" +echo "INFO: 2) Then, execute the web server with:" +echo "==============================" +echo "cd ../front" +echo "python3 main.py ${web_port}" +exit 0 diff --git a/src/scripts/util/checkfordocker.sh b/src/scripts/util/checkfordocker.sh new file mode 100755 index 000000000..f761f1114 --- /dev/null +++ b/src/scripts/util/checkfordocker.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set -e + +# Check for docker CLI +if ! command -v docker &> /dev/null; then + echo "docker could not be found. This script requires the docker CLI." + echo "see https://docs.docker.com/engine/install/ for installation instructions." + exit 1 +fi