User interface
This commit is contained in:
Breanna-Stryker 2021-03-23 13:37:58 -04:00 коммит произвёл GitHub
Родитель 08eae8edaf
Коммит 4ca3dd5924
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
78 изменённых файлов: 28786 добавлений и 190 удалений

Просмотреть файл

@ -27,17 +27,17 @@ jobs:
- name: get vars
run : |
cd build
cd src/build
./get_vars.sh
- name: login
run : |
cd build
cd src/build
./login_azcli.sh vars/mlz_tf_cfg.var
- name: apply terraform
run : |
cd build
cd src/build
./apply_tf.sh \
vars/mlz_tf_cfg.var \
vars/globals.tfvars \
@ -49,7 +49,7 @@ jobs:
- name: destroy terraform
run : |
cd build
cd src/build
./destroy_tf.sh \
vars/mlz_tf_cfg.var \
vars/globals.tfvars \

2
.github/workflows/validate-terraform.yml поставляемый
Просмотреть файл

@ -21,4 +21,4 @@ jobs:
- shell: bash
name: validate and lint terraform
run: |
build/validate_tf.sh
src/build/validate_tf.sh

11
.gitignore поставляемый
Просмотреть файл

@ -7,9 +7,15 @@
*.tfstate.backup
terraform-provider-azurerm_v*
terraform-provider-random_v*
*.terraform.lock.hcl
# Setup config variables file
mlz_tf_cfg.var
saca-hub.tfvars.json
tier-0.tfvars.json
tier-1.tfvars.json
tier-2.tfvars.json
globals.tfvars.json
# Bash artifacts
*.vars
@ -37,4 +43,7 @@ artifacts/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
*.pyc
**/.idea/
**/config_output/
**/exec_output

61
NOTICE Normal file
Просмотреть файл

@ -0,0 +1,61 @@
NOTICES
This repository incorporates material as listed below or described in the code.
Component: Bootstrap
Bootstrap Reboot v4.5.3 (https://getbootstrap.com/)
Copyright 2011-2020 The Bootstrap Authors
Copyright 2011-2020 Twitter, Inc.
Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
The MIT License (MIT)
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
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.
Component: jQuery v3.5.1 (c) JS Foundation and other contributors
https://jquery.org/license
Note: The license text for jquery redirects to https://tldrlegal.com/license/mit-license#fulltext
which is reproduced below, including placeholders for year and copyright holders.
The MIT License (MIT)
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
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.

156
README.md
Просмотреть файл

@ -1,138 +1,76 @@
# Mission LZ
Terraform resources to deploy Tier 0, 1, and 2, and the components of a [SACA hub](https://docs.microsoft.com/en-us/azure/azure-government/compliance/secure-azure-computing-architecture).
Mission Landing Zone is a highly opinionated template which IT oversight organizations can use to create a cloud management system to deploy Azure environments for their teams. It addresses a narrowly scoped, specific need for an SCCA compliant hub and spoke infrastructure.
## Getting Started
Mission LZ is:
1. Log in using the Azure CLI
- Designed for US Gov mission customers
- Implements [SCCA](https://docs.microsoft.com/en-us/azure/azure-government/compliance/secure-azure-computing-architecture) requirements following Microsoft's [SACA](https://aka.ms/saca) implementation guidance
- Deployable in commercial, government, and air-gapped Azure clouds
- A narrow scope for a specific common need
- A simple solution with low configuration
- Written in Terraform and Linux shell scripts
```BASH
az login
```
Mission Landing Zone is the right solution when:
1. [Configure the Terraform Backend](#Configure-the-Terraform-Backend)
1. [Set Terraform Configuration Variables](#Set-Terraform-Configuration-Variables)
1. [Deploy Terraform Configuration](#Deploy-Terraform-Configuration)
- A simple, secure, and scalable hub and spoke infrastructure is needed
- Various teams need separate, secure cloud environments administered by a central IT team
- There is a need to implement SCCA
- Hosting any workload requiring a secure environment, for example: data warehousing, AI/ML, and containerized applications
### Configure the Terraform Backend
Design goals include:
The MLZ deployment architecture uses a single Service Principal whose credentials are stored in a central "config" Key Vault. Terraform state storage is distributed into a separate storage account for each tier. When deploying the MLZ architecture, all tiers can be deployed into a single subscription or each tier can be deployed into its own subscription.
- A simple, minimal set of code that is easy to configure
- Good defaults that allow experimentation and testing in a single subscription
- Deployment via command line or with a user interface
- Uses Azure PaaS products
1. Create the `mlz_tf_cfg.var` file using the `mlz_tf_cfg.var.sample` as a template.
Our intent is to enable IT Admins to use this software to:
The information in the `mlz_tf_cfg.var` file, will be used by `mlz_tf_setup.sh` to create and populate a `config.vars` file for each tier and saved inside the deployment folder for each tier (example: \src\core\tier-0\config.vars).
- Test and evaluate the landing zone using a single Azure subscription
- Develop a known good configuration that can be used for production with multiple Azure subscriptions
- Optionally, customize the Terraform deployment configuration to suit specific needs
- Deploy multiple customer workloads in production
For example:
## Scope
```plaintext
mlz_env_name="{MLZ_ENV_NAME}"
mlz_config_location="{MLZ_CONFIG_LOCATION}"
```
Mission LZ has the following scope:
Would become:
- Hub and spoke networking intended to comply with SCCA controls
- Remote access
- Shared services, i.e., services available to all workloads via the networking hub
- Ability to create multiple workloads or team subscriptions
- Compatibility with SCCA compliance (and other compliance frameworks)
- Security using standard Azure tools with sensible defaults
```plaintext
mlz_env_name="dev"
mlz_config_location="eastus"
```
<!-- markdownlint-disable MD033 -->
<!-- allow html for images so that they can be sized -->
<img src="src/docs/images/scope.png" alt="Mission LZ Scope" width="600" />
<!-- markdownlint-enable MD033 -->
1. Run `mlz_tf_setup.sh` at [src/scripts/mlz_tf_setup.sh](src/scripts/mlz_tf_setup.sh) to create:
## Networking
- A config Resource Group to store the Key Vault
- Resource Groups for each tier to store the Terraform state Storage Account
- A Service Principal to execute terraform commands
- An Azure Key Vault to store the Service Principal's client ID and client secret
- A Storage Account and Container for each tier to store tier Terraform state files
- Tier specific Terraform backend config files
Networking is set up in a hub and spoke design, separated by tiers: T0, T1, T2, and multiple T3s. Security can be configured to allow separation of duties between all tiers. Most customers will deploy each tier to a separate Azure subscription, but multiple subscriptions are not required.
```bash
# usage mlz_tf_setup.sh: <mlz_tf_cfg.var path>
<!-- markdownlint-disable MD033 -->
<img src="src/docs/images/networking.png" alt="Mission LZ Networking" width="600" />
<!-- markdownlint-enable MD033 -->
chmod u+x src/scripts/mlz_tf_setup.sh
## Getting Started using Mission LZ
src/scripts/mlz_tf_setup.sh src/core/mlz_tf_cfg.var
```
See our [Getting Started Guide](src/docs/getting-started.md) in the docs.
### Set Terraform Configuration Variables
## Product Roadmap
First, clone the *.tfvars.sample file for the global Terraform configuration (e.g. [src/core/globals.tfvars.sample](src/core/globals.tfvars.sample)) and substitute placeholders marked by curly braces "{" and "}" with the values of your choosing.
Then, repeat this process, cloning the *.tfvars.sample file for the Terraform configuration(s) you are deploying and substitute placeholders marked by curly braces "{" and "}" with the values of your choosing.
For example:
```plaintext
location="{MLZ_LOCATION}" # the templated value in src/core/globals.tfvars.sample
```
Would become:
```plaintext
location="eastus" # the value used by Terraform in src/core/globals.tfvars
```
### Deploy Terraform Configuration
You can use `apply_terraform.sh` at [src/scripts/apply_terraform.sh](src/scripts/apply_terraform.sh) to both initialize Terraform and apply a Terraform configuration based on the backend environment variables and Terraform variables you've setup in previous steps.
The script `destroy_terraform.sh` at [src/scripts/destroy_terraform.sh](src/scripts/destroy_terraform.sh) is helpful during testing. This script is exactly like the
`apply_terraform.sh` except it destroys resources defined in the target state file
`apply_terraform.sh` and `destroy_terraform.sh` take two arguments:
1. The Global variables file
1. The directory that contains the main.tf and *.tfvars variables file of the configuration to apply
For example, from the root of this repository, you could apply Tier 0 with a command like:
```bash
src/scripts/apply_terraform.sh \
src/core/globals.tfvars \
src/core/tier-0
```
To apply Tier 1, you could then change the target directory:
```bash
src/scripts/apply_terraform.sh \
src/core/globals.tfvars \
src/core/tier-1
```
Repeating this same pattern, for whatever configuration you wanted to apply and reuse in some automated pipeline.
Use `init_terraform.sh` at [src/scripts/init_terraform.sh](src/scripts/init_terraform.sh) to perform just an initialization of the Terraform environment
To initialize Terraform for Tier 1, you could then change the target directory:
```bash
src/scripts/init_terraform.sh \
src/core/tier-1
```
### Terraform Providers
The development container definition downloads the required Terraform plugin providers during the container build so that the container can be transported to an air-gapped network for use. The container also sets the `TF_PLUGIN_CACHE_DIR` environment variable, which Terraform uses as the search location for locally installed providers. If you are not using the container to deploy or if the `TF_PLUGIN_CACHE_DIR` environment variable is not set, Terraform will automatically attempt to download the provider from the internet when you execute the `terraform init` command.
See the development container [README](.devcontainer/README.md) for more details on building and running the container.
## Helpful Links
For more endpoint mappings between AzureCloud and AzureUsGovernment: <https://docs.microsoft.com/en-us/azure/azure-government/compare-azure-government-global-azure#guidance-for-developers/>
See the [Projects](https://github.com/Azure/missionlz/projects) page for the release timeline and feature areas.
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit <https://cla.opensource.microsoft.com/>.
This project welcomes contributions and suggestions. See our [Contributing Guide](CONTRIBUTING.md) for details.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
## Feedback, Support, and How to Contact Us
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
Please see the [Support and Feedback Guide](SUPPORT.md). To report a security issue please see our [security guidance](./SECURITY.md).
## Trademarks

Просмотреть файл

@ -1,12 +1,16 @@
# Support
# Support and Feedback
## How to file issues and get help
This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates.
We welcome feedback of all types.
For new issues, file your bug or feature request as a new [issue](https://github.com/Azure/missionlz/issues).
This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates.
For help and questions about using this project, please submit a question as a new [issue](https://github.com/Azure/missionlz/issues) using the question template.
For new issues, file your bug or feature request as a new [issue](https://github.com/Azure/missionlz/issues), [bug](https://github.com/Azure/missionlz/issues), or [feature request](https://github.com/Azure/missionlz/issues).
For help and questions about using this project, please submit a [question](https://github.com/Azure/missionlz/issues).
To report a security issue please see our [security guidance](./SECURITY.md).
## Microsoft Support Policy

74
src/Dockerfile Normal file
Просмотреть файл

@ -0,0 +1,74 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
FROM ubuntu:20.04
# Instructs apt-get to run without a terminal
ENV DEBIAN_FRONTEND=noninteractive
# Update distro (software-properties-common installs the add-apt-repository command)
RUN apt-get update \
&& apt-get -y install --no-install-recommends apt-utils software-properties-common 2>&1 \
&& apt-get dist-upgrade -y \
&& add-apt-repository ppa:git-core/ppa \
&& apt-get install -y \
unzip \
wget \
python3 \
python3-pip
# Install Terraform and Python
RUN wget -O terraform.zip https://releases.hashicorp.com/terraform/0.14.3/terraform_0.14.3_linux_amd64.zip\
&& unzip ./terraform.zip -d /usr/local/bin/ \
&& rm terraform.zip
# Download Terraform providers (plugins)
# Setting the TF_PLUGIN_CACHE_DIR environment variable instructs Terraform to search that folder for plugins first
ENV TF_PLUGIN_CACHE_DIR=/usr/lib/tf-plugins
ARG AZURERM_LOCAL_PATH="${TF_PLUGIN_CACHE_DIR}/registry.terraform.io/hashicorp/azurerm/2.50.0/linux_amd64"
ARG RANDOM_LOCAL_PATH="${TF_PLUGIN_CACHE_DIR}/registry.terraform.io/hashicorp/random/3.1.0/linux_amd64"
ARG AZURERM_PROVIDER=https://releases.hashicorp.com/terraform-provider-azurerm/2.50.0/terraform-provider-azurerm_2.50.0_linux_amd64.zip
ARG RANDOM_PROVIDER=https://releases.hashicorp.com/terraform-provider-random/3.1.0/terraform-provider-random_3.1.0_linux_amd64.zip
RUN wget -O azurerm.zip ${AZURERM_PROVIDER} \
&& wget -O random.zip ${RANDOM_PROVIDER} \
&& mkdir -p ${AZURERM_LOCAL_PATH} \
&& mkdir -p ${RANDOM_LOCAL_PATH} \
&& unzip azurerm.zip -d ${AZURERM_LOCAL_PATH} \
&& unzip random.zip -d ${RANDOM_LOCAL_PATH} \
&& rm azurerm.zip \
&& rm random.zip
# Install the Microsoft package key
RUN wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
&& dpkg -i packages-microsoft-prod.deb \
&& rm packages-microsoft-prod.deb
# Install the Microsoft signing key
RUN curl -sL https://packages.microsoft.com/keys/microsoft.asc | \
gpg --dearmor | \
tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
# Install the AZ CLI repository
RUN AZ_REPO=$(lsb_release -cs) \
&& echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | \
tee /etc/apt/sources.list.d/azure-cli.list
# Install AZ CLI
RUN apt-get update && apt-get install -y azure-cli
ADD ./front* /deployment/front/
ADD ./scripts* /deployment/scripts/
ADD ./core* /deployment/core/
ADD ./modules* /deployment/modules/
ADD ./build* /deployment/build/
RUN pip3 install -r /deployment/front/requirements.txt
# Reset to the default value
ENV DEBIAN_FRONTEND=dialog
WORKDIR /deployment/front
ENTRYPOINT python3 main.py

Просмотреть файл

Просмотреть файл

@ -35,8 +35,8 @@ tier2_vars=$6
display_tf_output=${7:-n}
# reference paths
core_path=$(realpath ../src/core/)
scripts_path=$(realpath ../src/scripts/)
core_path=$(realpath ../core/)
scripts_path=$(realpath ../scripts/)
# apply function
apply() {
@ -63,7 +63,7 @@ apply() {
cp "${input_vars}" "${temp_vars}"
# remove any configuration tfvars and subtitute it with input vars
tf_vars="${path}/${name}.tfvars"
tf_vars="${path}/$(basename "${vars}")"
rm -f "${tf_vars}"
touch "${tf_vars}"
cp "${temp_vars}" "${tf_vars}"
@ -80,8 +80,8 @@ apply() {
attempts=1
max_attempts=5
apply_command="${scripts_path}/apply_terraform.sh ${globals} ${path} y"
destroy_command="${scripts_path}/destroy_terraform.sh ${globals} ${path} y"
apply_command="${scripts_path}/apply_terraform.sh ${globals} ${path} ${tf_vars} y"
destroy_command="${scripts_path}/destroy_terraform.sh ${globals} ${path} ${tf_vars} y"
if [[ $display_tf_output == "n" ]]; then
apply_command+=" &>/dev/null"

Просмотреть файл

@ -35,8 +35,8 @@ tier2_vars=$6
display_tf_output=${7:-n}
# reference paths
core_path=$(realpath ../src/core/)
scripts_path=$(realpath ../src/scripts/)
core_path=$(realpath ../core/)
scripts_path=$(realpath ../scripts/)
# destroy function
destroy() {
@ -63,7 +63,7 @@ destroy() {
cp "${input_vars}" "${temp_vars}"
# remove any configuration tfvars and subtitute it with input vars
tf_vars="${path}/${name}.tfvars"
tf_vars="${path}/$(basename "${vars}")"
rm -f "${tf_vars}"
touch "${tf_vars}"
cp "${temp_vars}" "${tf_vars}"
@ -80,7 +80,7 @@ destroy() {
attempts=1
max_attempts=5
destroy_command="${scripts_path}/destroy_terraform.sh ${globals} ${path} y"
destroy_command="${scripts_path}/destroy_terraform.sh ${globals} ${path} ${tf_vars} y"
if [[ "$display_tf_output" == "n" ]]; then
destroy_command+=" &>/dev/null"

44
src/build/front_wrapper.sh Executable file
Просмотреть файл

@ -0,0 +1,44 @@
#!/bin/bash
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
#
# shellcheck disable=SC1090,SC2154
# SC1090: Can't follow non-constant source. These values come from an external file.
# SC2154: "var is referenced but not assigned". These values come from an external file.
#
# Get the tenant ID from some MLZ configuration file and login using known Service Principal credentials
set -e
error_log() {
echo "${1}" 1>&2;
}
usage() {
echo "front_wrapper.sh: This provides a wrapper to get around python shell execution issues, it combines login and apply_tf"
error_log "usage: front_wrapper.sh <mlz config> <globals.tfvars> <saca.tfvars> <tier0.tfvars> <tier1.tfvars> <tier2.tfvars> <display terraform output (y/n)> <sp_app_id> <sp_secret_key>"
}
if [[ "$#" -lt 6 ]]; then
usage
exit 1
fi
mlz_config=$1
# source the variables from MLZ config
source "${mlz_config}"
sp_id=${8:-$MLZCLIENTID}
sp_pw=${9:-$MLZCLIENTSECRET}
# login with known credentials
az login --service-principal \
--user "${sp_id}" \
--password="${sp_pw}" \
--tenant "${mlz_tenantid}" \
--allow-no-subscriptions \
--output none
. "${BASH_SOURCE%/*}/apply_tf.sh" "${1}" "${2}" "${3}" "${4}" "${5}" "${6}" "${7}"

Просмотреть файл

Просмотреть файл

@ -17,7 +17,7 @@ error_log() {
usage() {
echo "login_azcli.sh: Get the tenant ID from some MLZ configuration file and login using known Service Principal credentials"
error_log "usage: login_azcli.sh <mlz config>"
error_log "usage: login_azcli.sh <mlz config> <SP_ID> <SP_PW>"
}
if [[ "$#" -lt 1 ]]; then
@ -30,10 +30,13 @@ mlz_config=$1
# source the variables from MLZ config
source "${mlz_config}"
sp_id=${2:-$MLZCLIENTID}
sp_pw=${3:-$MLZCLIENTSECRET}
# login with known credentials
az login --service-principal \
--user "${MLZCLIENTID}" \
--password="${MLZCLIENTSECRET}" \
--user "${sp_id}" \
--password="${sp_pw}" \
--tenant "${mlz_tenantid}" \
--allow-no-subscriptions \
--output none
--output json

Просмотреть файл

@ -22,7 +22,7 @@ fi
full_path=$(realpath "${0}")
repo_path=$(dirname "$(dirname "${full_path}")")
core_path="${repo_path}/src/core"
core_path="${repo_path}/core"
if [ -d "$core_path" ];
then

Просмотреть файл

@ -0,0 +1,41 @@
{
"globals": {
"form": [
{
"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",
"options": []
},
{
"varname": "mlz_cloud",
"type": "text",
"default_val": "AzureCloud",
"description": "Azure cloud being deployed to, # e.g. 'AzureCloud' or 'AzureUSGovernment', etc",
"options": []
},
{
"varname": "mlz_tenantid",
"type": "text",
"default_val": "env:TENANT_ID",
"description": "Tenant ID where your subscriptions liv",
"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'",
"options": []
},
{
"varname": "mlz_location",
"type": "text",
"default_val": "env:LOCATION",
"description": "The location that you're deploying to.",
"options": []
}
]
}
}

Просмотреть файл

@ -0,0 +1,7 @@
{
"tf_environment": "{TF_ENVIRONMENT}",
"mlz_cloud": "{MLZ_CLOUD}",
"mlz_tenantid": "{MLZ_TENANTID}",
"mlz_metadatahost": "{MLZ_METADATAHOST}",
"mlz_location": "{MLZ_REGION}"
}

Просмотреть файл

@ -0,0 +1,127 @@
{
"saca-hub": {
"form": [
{
"varname": "deploymentname",
"type": "text",
"default_val": "mlzci",
"description": "A unique name for your terraform deployment",
"options": []
},
{
"varname": "saca_subid",
"type": "text",
"default_val": "env:SUBSCRIPTION_ID",
"description": "The subscription id where the SACA hub lives",
"options": []
},
{
"varname": "saca_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-sacaci",
"description": "Resource group name",
"options": []
},
{
"varname": "saca_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-sacaci",
"description": "Virtual Network Name",
"options": []
},
{
"varname": "saca_lawsname",
"type": "text",
"default_val": "laws-eastus-mlz-sacaci",
"description": "Name for log analytic workspace",
"options": []
},
{
"varname": "vnet_address_space",
"type": "list",
"default_val": [
"10.0.100.0/24"
],
"description": "Virtual Network Address Space",
"options": []
},
{
"varname": "tier0_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-t0ci",
"description": "Tier 0 resource group name",
"options": []
},
{
"varname": "tier0_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-t0ci",
"description": "Tier 0 virtual network name",
"options": []
},
{
"varname": "tier1_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-t1ci",
"description": "Tier 1 resource group name",
"options": []
},
{
"varname": "tier1_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-t1ci",
"description": "Tier one virtual network name",
"options": []
},
{
"varname": "tier2_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-t1ci",
"description": "Tier 2 resource group name",
"options": []
},
{
"varname": "tier2_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-t2ci",
"description": "Tier 2 virtual network name",
"options": []
},
{
"varname": "firewall_address_space",
"type": "text",
"default_val": "10.0.100.0/26",
"description": "Address space for the firewall",
"options": []
},
{
"varname": "saca_fwname",
"type": "text",
"default_val": "DemoFirewall",
"description": "Saca Firewall Name",
"options": []
},
{
"varname": "firewall_ipconfig_name",
"type": "text",
"default_val": "FirewallIPConfiguration",
"description": "Name for the firewall ipconfig",
"options": []
},
{
"varname": "public_ip_name",
"type": "text",
"default_val": "FirewallPublicIP",
"description": "Name for the Public IP",
"options": []
},
{
"varname": "create_network_watcher",
"type": "boolean",
"default_val": false,
"description": "Do you need to create a network watcher here?",
"options": []
}
]
}
}

Просмотреть файл

@ -0,0 +1,21 @@
{
"deploymentname": "{SACA_DEPLOYMENTNAME}",
"saca_subid": "{SACA_SUBID}",
"saca_rgname": "{SACA_RGNAME}",
"saca_vnetname": "{SACA_VNETNAME}",
"saca_lawsname": "{SACA_LAWSNAME}",
"vnet_address_space": [
"{SACA_VNETSPACE}"
],
"tier0_rgname": "{TIER0_RGNAME}",
"tier0_vnetname": "{TIER0_VNETNAME}",
"tier1_rgname": "{TIER1_RGNAME}",
"tier1_vnetname": "{TIER1_VNETNAME}",
"tier2_rgname": "{TIER2_RGNAME}",
"tier2_vnetname": "{TIER2_VNETNAME}",
"firewall_address_space": "{SACA_FWSPACE}",
"saca_fwname": "{SACA_FWNAME}",
"firewall_ipconfig_name": "{SACA_FWIPCONFIGNAME}",
"public_ip_name": "{SACA_FWPIPNAME}",
"create_network_watcher": false
}

Просмотреть файл

@ -67,7 +67,7 @@ module "t0-network" {
location = azurerm_resource_group.t0.location
resource_group_name = azurerm_resource_group.t0.name
vnet_name = var.tier0_vnetname
vnet_address_space = var.vnet_address_space
vnet_address_space = var.tier0_vnet_address_space
log_analytics_workspace_id = data.azurerm_log_analytics_workspace.hub.id
tags = {

Просмотреть файл

@ -0,0 +1,134 @@
{
"tier-0": {
"str_maps": {
"TIER0_SUBNETVM_NAME": "subnets.{TIER0_SUBNETVM_NAME}.name"
},
"form": [
{
"varname": "saca_subid",
"type": "text",
"default_val": "env:SUBSCRIPTION_ID",
"description": "Saca Hub Subscription ID",
"options": []
},
{
"varname": "saca_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-sacaci",
"description": "Saca Hub Resource Group Name",
"options": []
},
{
"varname": "saca_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-sacaci",
"description": "Saca Virtual Network Name",
"options": []
},
{
"varname": "saca_fwname",
"type": "text",
"default_val": "DemoFirewallci",
"description": "Saca Firewall Name",
"options": []
},
{
"varname": "saca_lawsname",
"type": "text",
"default_val": "laws-eastus-mlz-sacaci",
"description": "Saca Log Analytic Workspace Name",
"options": []
},
{
"varname": "tier0_subid",
"type": "text",
"default_val": "env:SUBSCRIPTION_ID",
"description": "Tier0 Subscription Id",
"options": []
},
{
"varname": "tier0_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-t0ci",
"description": "Tier0 Resource Group Name",
"options": []
},
{
"varname": "tier0_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-t0ci",
"description": "Tier0 Virtual Network Name",
"options": []
},
{
"varname": "tier0_vnet_address_space",
"type": "list",
"default_val": [
"10.0.110.0/26"
],
"description": "A list of values (NewLine Separated) for vnet address space",
"options": []
},
{
"varname": "tier0_create_network_watcher",
"type": "boolean",
"default_val": false,
"description": "Whether to create the network watcher in this tier vnet.",
"options": []
},
{
"varname": "subnets.{TIER0_SUBNETVM_NAME}.name",
"type": "text",
"default_val": "tier0vms",
"description": "A unique name for the Tier0 Subnet",
"options": []
},
{
"varname": "subnets.{TIER0_SUBNETVM_NAME}.address_prefixes",
"type": "list",
"default_val": [
"10.0.110.0/27"
],
"description": "A list of values (NewLine Separated) for vnet address space",
"options": []
},
{
"varname": "subnets.{TIER0_SUBNETVM_NAME}.service_endpoints",
"type": "list",
"default_val": [
"Microsoft.Storage"
],
"description": "A list of values (NewLine Separated) for service endpoints",
"options": []
},
{
"varname": "subnets.{TIER0_SUBNETVM_NAME}.enforce_private_link_endpoint_network_policies",
"type": "boolean",
"default_val": false,
"description": "Enforce Private Link Endpoint Policies",
"options": []
},
{
"varname": "subnets.{TIER0_SUBNETVM_NAME}.enforce_private_link_service_network_policies",
"type": "boolean",
"default_val": false,
"description": "Enforce private link service network policies",
"options": []
},
{
"varname": "subnets.{TIER0_SUBNETVM_NAME}.nsg_name",
"type": "text",
"default_val": "tier0vmsnsg",
"description": "Unique name for Network Security Group",
"options": []
},
{
"varname": "subnets.{TIER0_SUBNETVM_NAME}.routetable_name",
"type": "text",
"default_val": "tier0vmsrtci",
"description": "Tier 0 Routeable Subnet Name",
"options": []
}
]
}
}

Просмотреть файл

@ -0,0 +1,54 @@
{
"deploymentname": "{TIER0_DEPLOYMENTNAME}",
"saca_subid": "{SACA_SUBID}",
"saca_rgname": "{SACA_RGNAME}",
"saca_vnetname": "{SACA_VNETNAME}",
"saca_fwname": "{SACA_FWNAME}",
"saca_lawsname": "{SACA_LAWSNAME}",
"tier0_subid": "{TIER0_SUBID}",
"tier0_rgname": "{TIER0_RGNAME}",
"tier0_vnetname": "{TIER0_VNETNAME}",
"tier0_vnet_address_space": [
"{TIER0_VNETSPACE}"
],
"subnets": {
"{TIER0_SUBNETVM_NAME}": {
"name": "{TIER0_SUBNETVM_NAME}",
"address_prefixes": [
"{TIER0_SUBNETVM_ADDRESSPREFIXLIST}"
],
"service_endpoints": [
"{TIER0_SUBNETVM_SERVICEENDPOINTLIST}"
],
"enforce_private_link_endpoint_network_policies": false,
"enforce_private_link_service_network_policies": false,
"nsg_name": "{TIER0_SUBNETVM_NSGNAME}",
"nsg_rules": {
"allow_ssh": {
"name": "allow_ssh",
"priority": "100",
"direction": "Inbound",
"access": "Allow",
"protocol": "Tcp",
"source_port_range": "",
"destination_port_range": "22",
"source_address_prefix": "*",
"destination_address_prefix": ""
},
"allow_rdp": {
"name": "allow_rdp",
"priority": "200",
"direction": "Inbound",
"access": "Allow",
"protocol": "Tcp",
"source_port_range": "",
"destination_port_range": "3389",
"source_address_prefix": "*",
"destination_address_prefix": ""
}
},
"routetable_name": "{TIER0_SUBNETVM_RTNAME}"
}
},
"create_network_watcher": false
}

Просмотреть файл

@ -18,7 +18,7 @@ tier0_vnetname = "{TIER0_VNETNAME}"
# Network configuration section
#################################
vnet_address_space = ["{TIER0_VNETSPACE}"]
tier0_vnet_address_space = ["{TIER0_VNETSPACE}"]
subnets = {
"{TIER0_SUBNETVM_NAME}" = {

Просмотреть файл

@ -73,7 +73,7 @@ variable "tier0_vnetname" {
#################################
# Network configuration section
#################################
variable "vnet_address_space" {
variable "tier0_vnet_address_space" {
description = "Address space prefixes list of strings"
type = list(string)
}

Просмотреть файл

@ -67,7 +67,7 @@ module "t1-network" {
location = azurerm_resource_group.t1.location
resource_group_name = azurerm_resource_group.t1.name
vnet_name = var.tier1_vnetname
vnet_address_space = var.vnet_address_space
vnet_address_space = var.tier1_vnet_address_space
log_analytics_workspace_id = data.azurerm_log_analytics_workspace.hub.id
tags = {

Просмотреть файл

@ -0,0 +1,134 @@
{
"tier-1": {
"str_maps": {
"TIER1_SUBNETVM_NAME": "subnets.{TIER1_SUBNETVM_NAME}.name"
},
"form": [
{
"varname": "saca_subid",
"type": "text",
"default_val": "env:SUBSCRIPTION_ID",
"description": "Saca Hub Subscription ID",
"options": []
},
{
"varname": "saca_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-sacaci",
"description": "Saca Hub Resource Group Name",
"options": []
},
{
"varname": "saca_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-sacaci",
"description": "Saca Virtual Network Name",
"options": []
},
{
"varname": "saca_fwname",
"type": "text",
"default_val": "DemoFirewallci",
"description": "Saca Firewall Name",
"options": []
},
{
"varname": "saca_lawsname",
"type": "text",
"default_val": "laws-eastus-mlz-sacaci",
"description": "Saca Log Analytic Workspace Name",
"options": []
},
{
"varname": "tier1_subid",
"type": "text",
"default_val": "env:SUBSCRIPTION_ID",
"description": "Tier0 Subscription Id",
"options": []
},
{
"varname": "tier1_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-t1ci",
"description": "Tier0 Resource Group Name",
"options": []
},
{
"varname": "tier1_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-t1ci",
"description": "Tier0 Virtual Network Name",
"options": []
},
{
"varname": "tier1_vnet_address_space",
"type": "list",
"default_val": [
"10.0.115.0/26"
],
"description": "A list of values (NewLine Separated) for vnet address space",
"options": []
},
{
"varname": "tier1_create_network_watcher",
"type": "boolean",
"default_val": false,
"description": "Whether to create the network watcher in this tier vnet.",
"options": []
},
{
"varname": "subnets.{TIER1_SUBNETVM_NAME}.name",
"type": "text",
"default_val": "tier1vms",
"description": "A unique name for the Tier0 Subnet",
"options": []
},
{
"varname": "subnets.{TIER1_SUBNETVM_NAME}.address_prefixes",
"type": "list",
"default_val": [
"10.0.115.0/27"
],
"description": "A list of values (NewLine Separated) for vnet address space",
"options": []
},
{
"varname": "subnets.{TIER1_SUBNETVM_NAME}.service_endpoints",
"type": "list",
"default_val": [
"Microsoft.Storage"
],
"description": "A list of values (NewLine Separated) for service endpoints",
"options": []
},
{
"varname": "subnets.{TIER1_SUBNETVM_NAME}.enforce_private_link_endpoint_network_policies",
"type": "boolean",
"default_val": false,
"description": "Enforce Private Link Endpoint Policies",
"options": []
},
{
"varname": "subnets.{TIER1_SUBNETVM_NAME}.enforce_private_link_service_network_policies",
"type": "boolean",
"default_val": false,
"description": "Enforce private link service network policies",
"options": []
},
{
"varname": "subnets.{TIER1_SUBNETVM_NAME}.nsg_name",
"type": "text",
"default_val": "tier1vmsnsg",
"description": "Unique name for Network Security Group",
"options": []
},
{
"varname": "subnets.{TIER1_SUBNETVM_NAME}.routetable_name",
"type": "text",
"default_val": "tier1vmsrtci",
"description": "Tier 0 Routeable Subnet Name",
"options": []
}
]
}
}

Просмотреть файл

@ -0,0 +1,54 @@
{
"deploymentname": "{TIER1_DEPLOYMENTNAME}",
"saca_subid": "{SACA_SUBID}",
"saca_rgname": "{SACA_RGNAME}",
"saca_vnetname": "{SACA_VNETNAME}",
"saca_fwname": "{SACA_FWNAME}",
"saca_lawsname": "{SACA_LAWSNAME}",
"tier1_subid": "{TIER1_SUBID}",
"tier1_rgname": "{TIER1_RGNAME}",
"tier1_vnetname": "{TIER1_VNETNAME}",
"tier1_vnet_address_space": [
"{TIER1_VNETSPACE}"
],
"subnets": {
"{TIER1_SUBNETVM_NAME}": {
"name": "{TIER1_SUBNETVM_NAME}",
"address_prefixes": [
"{TIER1_SUBNETVM_ADDRESSPREFIXLIST}"
],
"service_endpoints": [
"{TIER1_SUBNETVM_SERVICEENDPOINTLIST}"
],
"enforce_private_link_endpoint_network_policies": false,
"enforce_private_link_service_network_policies": false,
"nsg_name": "{TIER1_SUBNETVM_NSGNAME}",
"nsg_rules": {
"allow_ssh": {
"name": "allow_ssh",
"priority": "100",
"direction": "Inbound",
"access": "Allow",
"protocol": "Tcp",
"source_port_range": "",
"destination_port_range": "22",
"source_address_prefix": "*",
"destination_address_prefix": ""
},
"allow_rdp": {
"name": "allow_rdp",
"priority": "200",
"direction": "Inbound",
"access": "Allow",
"protocol": "Tcp",
"source_port_range": "",
"destination_port_range": "3389",
"source_address_prefix": "*",
"destination_address_prefix": ""
}
},
"routetable_name": "{TIER1_SUBNETVM_RTNAME}"
}
},
"create_network_watcher": false
}

Просмотреть файл

@ -18,7 +18,7 @@ tier1_vnetname = "{TIER1_VNETNAME}"
# Network configuration section
#################################
vnet_address_space = ["{TIER1_VNETSPACE}"]
tier1_vnet_address_space = ["{TIER1_VNETSPACE}"]
subnets = {
"{TIER1_SUBNETVM_NAME}" = {

Просмотреть файл

@ -73,7 +73,7 @@ variable "tier1_vnetname" {
#################################
# Network configuration section
#################################
variable "vnet_address_space" {
variable "tier1_vnet_address_space" {
description = "Address space prefixes for the virtual network"
type = list(string)
}

Просмотреть файл

@ -67,7 +67,7 @@ module "t2-network" {
location = azurerm_resource_group.t2.location
resource_group_name = azurerm_resource_group.t2.name
vnet_name = var.tier2_vnetname
vnet_address_space = var.vnet_address_space
vnet_address_space = var.tier2_vnet_address_space
log_analytics_workspace_id = data.azurerm_log_analytics_workspace.hub.id
tags = {

Просмотреть файл

@ -0,0 +1,134 @@
{
"tier-2": {
"str_maps": {
"TIER2_SUBNETVM_NAME": "subnets.{TIER2_SUBNETVM_NAME}.name"
},
"form": [
{
"varname": "saca_subid",
"type": "text",
"default_val": "env:SUBSCRIPTION_ID",
"description": "Saca Hub Subscription ID",
"options": []
},
{
"varname": "saca_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-sacaci",
"description": "Saca Hub Resource Group Name",
"options": []
},
{
"varname": "saca_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-sacaci",
"description": "Saca Virtual Network Name",
"options": []
},
{
"varname": "saca_fwname",
"type": "text",
"default_val": "DemoFirewallci",
"description": "Saca Firewall Name",
"options": []
},
{
"varname": "saca_lawsname",
"type": "text",
"default_val": "laws-eastus-mlz-sacaci",
"description": "Saca Log Analytic Workspace Name",
"options": []
},
{
"varname": "tier2_subid",
"type": "text",
"default_val": "env:SUBSCRIPTION_ID",
"description": "Tier0 Subscription Id",
"options": []
},
{
"varname": "tier2_rgname",
"type": "text",
"default_val": "rg-eastus-mlz-t2ci",
"description": "Tier2 Resource Group Name",
"options": []
},
{
"varname": "tier2_vnetname",
"type": "text",
"default_val": "vn-eastus-mlz-t2ci",
"description": "Tier2 Virtual Network Name",
"options": []
},
{
"varname": "tier2_vnet_address_space",
"type": "list",
"default_val": [
"10.0.120.0/26"
],
"description": "A list of values (NewLine Separated) for vnet address space",
"options": []
},
{
"varname": "tier2_create_network_watcher",
"type": "boolean",
"default_val": false,
"description": "Whether to create the network watcher in this tier vnet.",
"options": []
},
{
"varname": "subnets.{TIER2_SUBNETVM_NAME}.name",
"type": "text",
"default_val": "tier2vms",
"description": "A unique name for the Tier0 Subnet",
"options": []
},
{
"varname": "subnets.{TIER2_SUBNETVM_NAME}.address_prefixes",
"type": "list",
"default_val": [
"10.0.120.0/27"
],
"description": "A list of values (NewLine Separated) for vnet address space",
"options": []
},
{
"varname": "subnets.{TIER2_SUBNETVM_NAME}.service_endpoints",
"type": "list",
"default_val": [
"Microsoft.Storage"
],
"description": "A list of values (NewLine Separated) for service endpoints",
"options": []
},
{
"varname": "subnets.{TIER2_SUBNETVM_NAME}.enforce_private_link_endpoint_network_policies",
"type": "boolean",
"default_val": false,
"description": "Enforce Private Link Endpoint Policies",
"options": []
},
{
"varname": "subnets.{TIER2_SUBNETVM_NAME}.enforce_private_link_service_network_policies",
"type": "boolean",
"default_val": false,
"description": "Enforce private link service network policies",
"options": []
},
{
"varname": "subnets.{TIER2_SUBNETVM_NAME}.nsg_name",
"type": "text",
"default_val": "tier2vmsnsg",
"description": "Unique name for Network Security Group",
"options": []
},
{
"varname": "subnets.{TIER2_SUBNETVM_NAME}.routetable_name",
"type": "text",
"default_val": "tier2vmsrtci",
"description": "Tier 0 Routeable Subnet Name",
"options": []
}
]
}
}

Просмотреть файл

@ -0,0 +1,52 @@
{
"deploymentname":"{TIER2_DEPLOYMENTNAME}",
"saca_subid":"{SACA_SUBID}",
"saca_rgname":"{SACA_RGNAME}",
"saca_vnetname":"{SACA_VNETNAME}",
"saca_fwname":"{SACA_FWNAME}",
"saca_lawsname":"{SACA_LAWSNAME}",
"tier2_subid":"{TIER2_SUBID}",
"tier2_rgname":"{TIER2_RGNAME}",
"tier2_vnetname":"{TIER2_VNETNAME}",
"tier2_vnet_address_space":["{TIER2_VNETSPACE}"],
"subnets": {
"{TIER2_SUBNETVM_NAME}": {
"name": "{TIER2_SUBNETVM_NAME}",
"address_prefixes": [
"{TIER2_SUBNETVM_ADDRESSPREFIXLIST}"
],
"service_endpoints": [
"{TIER2_SUBNETVM_SERVICEENDPOINTLIST}"
],
"enforce_private_link_endpoint_network_policies": false,
"enforce_private_link_service_network_policies": false,
"nsg_name": "{TIER2_SUBNETVM_NSGNAME}",
"nsg_rules": {
"allow_ssh": {
"name": "allow_ssh",
"priority": "100",
"direction": "Inbound",
"access": "Allow",
"protocol": "Tcp",
"source_port_range": "",
"destination_port_range": "22",
"source_address_prefix": "*",
"destination_address_prefix": ""
},
"allow_rdp": {
"name": "allow_rdp",
"priority": "200",
"direction": "Inbound",
"access": "Allow",
"protocol": "Tcp",
"source_port_range": "",
"destination_port_range": "3389",
"source_address_prefix": "*",
"destination_address_prefix": ""
}
},
"routetable_name": "{TIER2_SUBNETVM_RTNAME}"
}
},
"create_network_watcher": false
}

Просмотреть файл

@ -18,7 +18,7 @@ tier2_vnetname = "{TIER2_VNETNAME}"
# Network configuration section
#################################
vnet_address_space = ["{TIER2_VNETSPACE}"]
tier2_vnet_address_space = ["{TIER2_VNETSPACE}"]
subnets = {
"{TIER2_SUBNETVM_NAME}" = {

Просмотреть файл

@ -73,7 +73,7 @@ variable "tier2_vnetname" {
#################################
# Network configuration section
#################################
variable "vnet_address_space" {
variable "tier2_vnet_address_space" {
description = "Address space prefixes list of strings"
type = list(string)
}

18
src/docker-compose.yml Normal file
Просмотреть файл

@ -0,0 +1,18 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# This docker-compose file defines a container that runs the ez deploy application for Mission LZ.
# To run the containers:
# 1. Install Docker Desktop or Docker CE
# 2. In BASH or PowerShell, navigate to the folder containing this file.
# 3. Run this command: docker-compose up -d
version: '3.8'
services:
webfront:
build:
context: .
dockerfile: Dockerfile
container_name: "mlzfront"
ports:
- "80:80"

Просмотреть файл

@ -0,0 +1,119 @@
# Command-Line Deployment
## Step-by-step
1. Log in using the Azure CLI
```BASH
az login
```
1. [Configure the Terraform Backend](#Configure-the-Terraform-Backend)
1. [Set Terraform Configuration Variables](#Set-Terraform-Configuration-Variables)
1. [Deploy Terraform Configuration](#Deploy-Terraform-Configuration)
### Configure the Terraform Backend
The MLZ deployment architecture uses a single Service Principal whose credentials are stored in a central "config" Key Vault. Terraform state storage is distributed into a separate storage account for each tier. When deploying the MLZ architecture, all tiers can be deployed into a single subscription or each tier can be deployed into its own subscription.
1. Create the `mlz_tf_cfg.var` file using the `mlz_tf_cfg.var.sample` as a template.
The information in the `mlz_tf_cfg.var` file, will be used by `mlz_tf_setup.sh` to create and populate a `config.vars` file for each tier and saved inside the deployment folder for each tier (example: \src\core\tier-0\config.vars).
For example:
```plaintext
mlz_env_name="{MLZ_ENV_NAME}"
mlz_config_location="{MLZ_CONFIG_LOCATION}"
```
Would become:
```plaintext
mlz_env_name="dev"
mlz_config_location="eastus"
```
1. Run `mlz_tf_setup.sh` at [src/scripts/mlz_tf_setup.sh](/src/scripts/mlz_tf_setup.sh) to create:
- A config Resource Group to store the Key Vault
- Resource Groups for each tier to store the Terraform state Storage Account
- A Service Principal to execute terraform commands
- An Azure Key Vault to store the Service Principal's client ID and client secret
- A Storage Account and Container for each tier to store tier Terraform state files
- Tier specific Terraform backend config files
```bash
# usage mlz_tf_setup.sh: <mlz_tf_cfg.var path>
chmod u+x src/scripts/mlz_tf_setup.sh
src/scripts/mlz_tf_setup.sh src/core/mlz_tf_cfg.var
```
### Set Terraform Configuration Variables
First, clone the *.tfvars.sample file for the global Terraform configuration (e.g. [src/core/globals.tfvars.sample](/src/core/globals.tfvars.sample)) and substitute placeholders marked by curly braces "{" and "}" with the values of your choosing.
Then, repeat this process, cloning the *.tfvars.sample file for the Terraform configuration(s) you are deploying and substitute placeholders marked by curly braces "{" and "}" with the values of your choosing.
For example:
```plaintext
location="{MLZ_LOCATION}" # the templated value in src/core/globals.tfvars.sample
```
Would become:
```plaintext
location="eastus" # the value used by Terraform in src/core/globals.tfvars
```
### Deploy Terraform Configuration
You can use `apply_terraform.sh` at [src/scripts/apply_terraform.sh](/src/scripts/apply_terraform.sh) to both initialize Terraform and apply a Terraform configuration based on the backend environment variables and Terraform variables you've setup in previous steps.
The script `destroy_terraform.sh` at [src/scripts/destroy_terraform.sh](/src/scripts/destroy_terraform.sh) is helpful during testing. This script is exactly like the
`apply_terraform.sh` except it destroys resources defined in the target state file
`apply_terraform.sh` and `destroy_terraform.sh` take two arguments:
1. The Global variables file
1. The directory that contains the main.tf and *.tfvars variables file of the configuration to apply
For example, from the root of this repository, you could apply Tier 0 with a command like:
```bash
src/scripts/apply_terraform.sh \
src/core/globals.tfvars \
src/core/tier-0
```
To apply Tier 1, you could then change the target directory:
```bash
src/scripts/apply_terraform.sh \
src/core/globals.tfvars \
src/core/tier-1
```
Repeating this same pattern, for whatever configuration you wanted to apply and reuse in some automated pipeline.
Use `init_terraform.sh` at [src/scripts/init_terraform.sh](/src/scripts/init_terraform.sh) to perform just an initialization of the Terraform environment
To initialize Terraform for Tier 1, you could then change the target directory:
```bash
src/scripts/init_terraform.sh \
src/core/tier-1
```
### Terraform Providers
The development container definition downloads the required Terraform plugin providers during the container build so that the container can be transported to an air-gapped network for use. The container also sets the `TF_PLUGIN_CACHE_DIR` environment variable, which Terraform uses as the search location for locally installed providers. If you are not using the container to deploy or if the `TF_PLUGIN_CACHE_DIR` environment variable is not set, Terraform will automatically attempt to download the provider from the internet when you execute the `terraform init` command.
See the development container [README](/.devcontainer/README.md) for more details on building and running the container.
## Helpful Links
For more endpoint mappings between AzureCloud and AzureUsGovernment: <https://docs.microsoft.com/en-us/azure/azure-government/compare-azure-government-global-azure#guidance-for-developers/>

Просмотреть файл

@ -0,0 +1,33 @@
# Getting Started
## Concepts
### Command Line or User Interface (UI) Deployments
You can deploy Mission LZ from your workstation, or from the Mission LZ user interface deployed and hosted in your Azure subscription. Some other configurations are possible, but these are the two simplest paths.
The command-line deployments involve (1) defining configuration settings (we have examples and defaults to make this easier), and (2) running shell scripts that consolidate and wrap the Terraform commands. We strongly recommend using these scripts because they were designed to be usable, but you could also run the Terraform templates directly using the `terraform` command line tool.
The user interface deployment runs in a container in your Azure subscription. There is a command that will build the container image on your machine, upload the container image to Azure, and run the container in Azure Container Instances (ACI). You browse to the user interface using a link generated by the UI deployment, log into the UI with your Azure Active Directory (AAD) credentials, fill out the configuration settings, and then press the button to deploy. The UI generates and runs the command-line deployment using your configuration settings.
### Use the Development Container for Command Line Deployments
If you are planning to deploy from your local workstation, we recommend using the VS Code development container specified in this repository. The container includes all the tools and pre-requisites, but you have to build and run the container. If you have Docker Desktop installed, then VS Code makes the rest of it easy. See the [README](../../.devcontainer/README.md) document in the `.devcontainer` folder for details.
If you want to deploy from the command line on your workstation but do not want to use the develompent container, take a look at the [`Dockerfile`](../../.devcontainer/Dockerfile) and the [`devcontainer.json`](../../.devcontainer/Dockerfile) file for examples on how to configure your environment.
The develoment container is not necessary if you want to use the Mission LZ user interface for deployments. Docker Desktop or Docker CE is still required to build the user interface container.
## Pre-Requisites
Operating system: Mac OS, Linux, or Windows 10 with Windows Subsystem for Linux (WSL) or a Linux virtual machine. (We developed this on Windows 10/WSL running Ubuntu 20.04.)
Docker: Docker Desktop or Docker CE (We use Docker Desktop on Windows 10, integrated with WSL.)
The Azure CLI.
All other tools and resources are in the development container and in the user interface container. The simplest path is to deploy from one of these containers, but it is not required if you want to configure your own deployment environment. See the development container [README](../../.devcontainer/README.md) document in the `.devcontainer` folder for details, and the user interface [Dockerfile](../Dockerfile) for details on user interface pre-requisites.
## Step-by-step
See the detailed step-by-step guides for [Command Line Deployments](command-line-deployment.md) and [User Interface Deployments](ui-deployment.md).

Двоичные данные
src/docs/images/networking.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 38 KiB

Двоичные данные
src/docs/images/scope.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 37 KiB

134
src/docs/ui-deployment.md Normal file
Просмотреть файл

@ -0,0 +1,134 @@
# 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.
## General Requirements
In order to run this software, you'll need to install some requirements, regardless of the path you choose to take for execution. Follow the General Requirements, and then follow instructions for either remote or local installation
For any of the following options you will need docker on your machine. If you are pre-packaging and deploying on a target network, you will need docker locally installed on both your local internet connected machine, and your target machine. The below instructions might need to be found in your target environment to replicate.
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.
## Step-By-Step
[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.
### Step-by-Step Azure Installation
This process will build the user interface container image on your workstation using Docker, upload the container image to your Azure subscription, and install an instance of the container in Azure Container Instances (ACI). You'll need to have Docker installed locally, as well as the Azure Bash CLI.
From the "src" directory
```BASH
chmod u+x ./scripts/setup_ezdeploy.sh
./script/setup_ezdeploy.sh -d build -s <subscription_id> -t <tenant_id> -l <location> -e <tf_env_name> -m <mlz_env_name> -p port -0 <saca_subscription_id> -1 <tier0_subscription_id> -2 <tier1_subscription_id> -3 <tier2_subscription_id>"
```
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.
1. [Install Python](#Install-Python)
1. [Run the User Interface Locally](#Run-the-User-Interface-Locally)
### Install Python
Basic Installation Instructions for Ubuntu 20.04. You may need to find different instructions for other flavors of Linux.
```BASH
apt-get update \
&& apt-get install -y \
python3 \
python3-pip \
&& ln -s /usr/bin/python3 /usr/bin/python \
&& ln -s /usr/bin/pip3 /usr/bin/pip
```
### Run the User Interface Locally
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
az login
```
prior to following the following instructions
1. Install and Source a Python Virtual Environment
```bash
python3 -m venv /path/to/new/virtual/environment
source /path/to/new/virtual/environment/bin/activate
```
2. Install requirements via pip
```BASH
pip install -r src/front/requirements.txt
```
3. 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 <subscription_id> -t <tenant_id> -l <location> -e <tf_env_name> -m <mlz_env_name> -p port p -0 <saca_subscription_id> -1 <tier0_subscription_id> -2 <tier1_subscription_id> -3 <tier2_subscription_id>"
```
4. 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_SECRET"
$env:TENANT_ID="<TENANT_ID>"
$env:LOCATION='<CLOUD_LOCATION>'
$env:SUBSCRIPTION_ID='<SUBSCRIPTION_ID>'
$env:TF_ENV='<TERRAFORM_ENVIRONMENT>'
$env:MLZ_ENV='<ENVIRONMENT_NAME>'
```
```bash
export CLIENT_ID="<CLIENT_ID>"
export CLIENT_SECRET="<CLIENT_SECRET"
export TENANT_ID="<TENANT_ID>"
export LOCATION='<CLOUD_LOCATION>'
export SUBSCRIPTION_ID='<SUBSCRIPTION_ID>'
export TF_ENV='<TERRAFORM_ENVIRONMENT>'
export MLZ_ENV='<ENVIRONMENT_NAME>'
```
5. Execute web server
```bash
python main.py <port_if_not_80>
```
You can then access the application by pointing your browser at "localhost".

Просмотреть файл

75
src/front/lib/auth.py Normal file
Просмотреть файл

@ -0,0 +1,75 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Login Code from Examples at: https://github.com/Azure-Samples/ms-identity-python-webapp
import msal
import os
from starlette.requests import Request
# TODO: Change all below items to keyvault reads
CLIENT_ENV_ID = os.getenv("CLIENT_ID", "None")
CLIENT_ENV_SECRET = os.getenv("CLIENT_SECRET", "None")
AUTHORITY = "https://login.microsoftonline.com/" + os.getenv("TENANT_ID")
# You can find the proper permission names from this document
# https://docs.microsoft.com/en-us/graph/permissions-reference
SCOPE = ["User.ReadBasic.All"]
def load_cache(request: Request):
"""
Process a cache that exists in the users cookies
:param request: request object sent to the calling functions body
:return: Returns the processed cache from the users session
"""
cache = msal.SerializableTokenCache()
cookie_cache = request.cookies.get("token_cache")
if cookie_cache:
cache.deserialize(cookie_cache)
return cache
def build_msal_app(cache=None, client_id=CLIENT_ENV_ID, authority=None, secret=CLIENT_ENV_SECRET):
"""
Build the MSAL application for providing authentication
:param cache: provides the cache from the stored user cookies
:param client_id: The client ID of the AAD application being used for login
:param authority: Azure Authority for the application
:param secret: Client Secret of the AAd application being used for login
:return: Returns the app block used to facilitate login
"""
return msal.ConfidentialClientApplication(
client_id, authority=authority or AUTHORITY,
client_credential=secret, token_cache=cache)
def build_auth_code_flow(authority=None, scopes=None, redirect_uri=None, client_id=CLIENT_ENV_ID, secret=CLIENT_ENV_SECRET):
"""
Use the MSAL app to build a flow cache to facilitate logging in
:param authority: Azure Authority
:param scopes: The scopes being requested for this authorization
:param redirect_uri: The redirect URI to provide to the AAD login, must be a URI registered to the AAD app
:param client_id: The client ID of the AAD application being used for login
:param secret: Client Secret of the AAd application being used for login
:return: Returns the app flow code block used to provide URL's for login
"""
return build_msal_app(client_id=client_id, authority=authority, secret=secret).initiate_auth_code_flow(
scopes or [],
redirect_uri=redirect_uri)
"""
#TODO: purge when sure this won't be useful
Unused Example from MSAL examples
def get_token_from_cache(request: Request, scope=None):
cache = load_cache(request.cookies.get("token_cache")) # This web app maintains one cache per session
cca = build_msal_app(cache=cache)
accounts = cca.get_accounts()
if accounts: # So all account(s) belong to the current signed-in user
result = cca.acquire_token_silent(scope, account=accounts[0])
return result
"""

108
src/front/lib/utils.py Normal file
Просмотреть файл

@ -0,0 +1,108 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Provides a set of utility functions to be called from the primary API
import os
import json
from dominate.tags import *
from typing import Union
def dotted_write(prop_name: str, val: Union[int, str], target_dict: dict):
"""
Purpose: Function takes in a property value to be mapped that contains .'s in a string.
These .'s are resolved to a position in the dictionary in which the value will be written.
This function takes advantage of python's pass by value and embeds a value into an originating dictionary
that will be re-jsonified and written back to it's location.
:prop_name: a property value from a front end map dict
:val: the value to be written tot he target_dict
:target_dict: the dictionary that the value will be written into
"""
if "." in prop_name:
write_name = "target_dict"
nest_test = json.loads(json.dumps(target_dict))
keys = prop_name.split(".")
failed_test = False
for loc in keys:
if loc in nest_test:
write_name += '["'+loc+'"]'
nest_test = nest_test[loc]
else:
failed_test = True
if not failed_test:
exec(write_name + " = val")
else:
if prop_name in target_dict:
target_dict[prop_name] = val
def find_config(dir_scan=['../core', '../modules'], extension=".front.json"):
"""
Purpose: Function takes a list of directory names. Performs an os.walk to find *.front.json files and returns a
dictionary of file names and their contents.
If .orig is in place on the file, it's removed.
:dir_scan: the list of directories to be scanned.
:extension: the extension to look for and return in the scan
"""
config_files = {}
for config_dir in dir_scan:
walk = os.walk(os.path.join(os.getcwd(), config_dir))
for root, _, files in walk:
for f_name in files:
if extension in f_name:
cur_file = os.path.join(root, f_name)
config_files[cur_file.replace(".orig", "")] = json.load(open(cur_file))
return config_files
def build_form(form_doc: dict):
"""
Purpose: Function takes in a json document that describes a form, and returns the resulting dominate based
form to be appended to the front end UI
:form_doc: a dictionary derived from a loaded json
"""
doc_form = form(id="terraform_config", action="/execute", method="post")
doc_tabs = ul(cls="nav nav-tabs", id="myTab", role="tablist")
doc_panels = div(cls="tab-content")
for f_name, doc in form_doc.items():
for title, config in doc.items():
append_str = ""
if "saca" in title:
append_str = " active"
doc_tabs.add(li(a(title, href="#" + title, cls="nav-link" + append_str, data_toggle="tab"), cls="nav-item",
role="presentation"))
doc_panel = div(role="tabpanel", cls="tab-pane fade show custom-pane" + append_str, id=title)
with doc_panel:
for el_item in config["form"]:
with div(cls="form-elements"):
label(el_item["description"], cls="breadcrumb", label_for=el_item["varname"])
with div(cls="input-group input-group-sm mb-3"):
with div(cls="input-group-prepend"):
# Process environment options
if type(el_item["default_val"]) != bool:
if "env:" in el_item["default_val"]:
el_item["default_val"] = os.getenv(el_item["default_val"].replace("env:", ""), "")
span(el_item["varname"], cls="input-group-text")
if el_item["type"] == "text":
input_(id=el_item["varname"], cls="form-control", value=el_item["default_val"], name=el_item["varname"])
elif el_item["type"] == "list":
textarea("\n".join(el_item["default_val"]), id=el_item["varname"], cls="form-control", name="listinput:"+el_item["varname"], rows="4", columns="25")
elif el_item["type"] == "select":
select((option(x, value=x) for x in el_item["options"]), cls="form-control",
default=el_item["default_val"], name=el_item["varname"], id=el_item["varname"])
elif el_item["type"] == "boolean":
span(
input_(type="checkbox", default=bool(el_item["default_val"]), name=el_item["varname"], id=el_item["varname"])
, cls="input-group-text")
doc_panels.add(doc_panel)
doc_form.add(doc_tabs)
doc_form.add(doc_panels)
with doc_form:
input_(value="Execute Terraform", type="submit")
return doc_form

395
src/front/main.py Normal file
Просмотреть файл

@ -0,0 +1,395 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import json
from datetime import datetime, timedelta
import dominate
import uvicorn
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from dominate.tags import *
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from starlette.requests import Request
from lib import auth
from subprocess import call
import asyncio
import os
import re
import sys
from lib.utils import *
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
# Setup keyvault accesses to gather keys
keyVaultName = os.getenv("KEYVAULT_ID", None)
if keyVaultName:
keyVaultUrl = "https://{}.vault.azure.net/".format(keyVaultName)
# This will use your Azure Managed Identity
credential = DefaultAzureCredential()
secret_client = SecretClient(
vault_url=keyVaultUrl,
credential=credential)
static_location = '/static/'
exec_output = os.path.join(os.getcwd(), "exec_output", "exec.txt")
if not os.path.exists(os.path.join(os.getcwd(), "config_output")):
os.mkdir(os.path.join(os.getcwd(), "config_output"))
if not os.path.exists(os.path.join(os.getcwd(), "exec_output")):
os.mkdir(os.path.join(os.getcwd(), "exec_output"))
if not os.path.exists(exec_output):
with open(exec_output, "w+") as f:
f.write("")
@app.get("/")
async def home(request: Request):
"""
Primary landing section for the app
:param request: request object sent in the post body when accessing this API
:return: Will return a rendered HTML page
"""
# Handle the rendering of the login url
login_url = ""
user = ""
flow = request.cookies.get("flow")
if not request.cookies.get("user"):
if keyVaultName:
flow = auth.build_auth_code_flow(client_id=secret_client.get_secret("login-app-clientid").value,
secret=secret_client.get_secret("login-app-pwd").value, scopes=auth.SCOPE)
else:
flow = auth.build_auth_code_flow(scopes=auth.SCOPE)
login_url = flow["auth_uri"]
else:
user = json.loads(request.cookies.get("user"))
# Modal Templates
tenant_input_html = "Please enter the Tenant ID for the Azure Instance you are using. This is needed to set up" \
"authentication. You can find this by typing \"Tenant\" into the Azure Portal for where you" \
" intend to deploy your infrastructure." + \
br().render() + \
div(
input_("Tenant ID", id="tenantId", cls="form-control", aria_label="Small",
aria_describedby="inputGroup-sizing-sm"),
div(
button("Save Id", type="button", id="saveTenant",
cls="btn btn-outline-secondary"),
cls="input-group-append"),
cls="input-group input-group-sm mb-3").render(pretty=False)
login_input_html = "Click the below button to initiate AAD authentication" + br().render() + div(
a("Login", href=login_url),
cls="input-group input-group-sm mb-3").render(pretty=False)
try:
config = json.load(open("form_config.json"))
except FileNotFoundError:
config = None
# Initiate the HTML Document
doc = dominate.document(title="Mission LZ")
# Set initial style sheet in HTML doc head
with doc.head:
link(rel='stylesheet', href=static_location + 'bootstrap.min.css')
link(rel='stylesheet', href=static_location + 'custom.css')
# Fill in the single page app template with forms and sections
with doc:
# Case for each variable type in JSON definition
with div(cls="navbar navbar-expand-lg fixed-top navbar-dark bg-primary"):
with div(cls="container"):
a("Mission LZ", cls="navbar-brand", href="#")
with div(cls='text-right'):
if user:
a("Logout " + user["name"], href="/logout", cls="btn btn-outline-secondary")
with div(cls="container"):
with div(cls="page-header"):
if config:
pass
# Parse each config element in a switch to generate a section of forms for it
else:
build_form(find_config())
# Modal
with div(cls="modal fade", data_backdrop="static", id="promptModal", tabindex="-1", role="dialog",
aria_hidden="true"):
with div(cls="modal-dialog modal-dialog-centered modal-lg", role="document"):
with div(cls="modal-content"):
with div(cls="modal-header"):
h5("Modal Title", cls="modal-title")
div("Modal Content", cls="modal-body")
with div(cls="modal-footer"):
button("Close", id="modBtn", type="button", cls="btn btn-secondary", data_dismiss="modal")
# Include the Javascript Files in the output HTML
script(src=static_location + 'jquery-3.5.1.min.js')
script(src=static_location + 'bootstrap.bundle.min.js')
script(src=static_location + 'custom.js')
script().add_raw_string("""
function promptLogin() {
$('#promptModal').on('show.bs.modal', function (event) {
var modal = $(this)
modal.find('.modal-title').text('Login')
modal.find('.modal-body').html('""" + login_input_html + """')
modal.find('#modBtn').hide()
})
$('#promptModal').modal('show')
}
var interval = null
function retrieve_results() {
$.ajax({
type: "GET",
url: "/poll",
success: function(msg){
$("#terminal").text(msg);
}
});
}
function submitForm(){
interval = setInterval(retrieve_results, 2000);
}
$(document).ready(function(){
$('#showTenant').click(function(){
promptTenant()
})
logged_in = readCookie("user")
if(!logged_in || $.trim(logged_in) == ""){
promptLogin()
}
})
$(document).on('submit', '#terraform_config', function(e) {
$('#promptModal').on('show.bs.modal', function (event) {
var modal = $(this)
modal.find('.modal-title').text('Polling execution results: (Box May Stay Blank for an extended amount of time)')
modal.find('.modal-body').html('<pre id="terminal"></pre>')
modal.find('#modBtn').show()
modal.find('#modBtn').click(function(){
clearInterval(interval)
})
})
$('#promptModal').modal('show')
$.ajax({
url: $(this).attr('action'),
type: $(this).attr('method'),
data: $(this).serialize(),
success: function(html) {
submitForm()
}
});
e.preventDefault();
});
$("input").change(function(){
$("input[name="+ this.name +"]").val(this.value)
})
""")
response = HTMLResponse(content=str(doc), status_code=200)
response.set_cookie("flow", json.dumps(flow), expires=3600)
return response
# Display currently cached creds
@app.get("/creds")
async def display_creds(request: Request):
"""
Process the body request for debugging purposes if there's a user issue with Azure
:param request: request object sent in the post body when accessing this API
:return: Will return a json block with relevant debugging data for the user session
"""
result = request.cookies.get("flow")
user = request.cookies.get("user")
if keyVaultName:
test_value = secret_client.get_secret("login-app-clientid")
return JSONResponse({"creds": user, "flow": result, "test_val": test_value.value})
# Process Logout
@app.get("/logout")
async def process_logout():
"""
Purge the login information from the users session/cookie data
:return: Redirect to main body
"""
# Simply destroy the cookies in this session and get rid of the creds, redirect to landing
response = RedirectResponse("/") # Process the destruction from main app/test result
response.delete_cookie("user")
response.delete_cookie("flow")
return response
# API To Capture the redirect from Azure
@app.get("/redirect")
async def capture_redirect(request: Request):
"""
Process the request body that's returned from AAD. This will process the login items to acquire user in info
:param request: request object sent in the post body when accessing this API
:return: Will either redirect the user back to the main page, or display an error
"""
try:
cache = auth.load_cache(request)
if keyVaultName:
result = auth.build_msal_app(cache, client_id=secret_client.get_secret("login-app-clientid").value,
secret=secret_client.get_secret(
"login-app-pwd").value).acquire_token_by_auth_code_flow(
dict(json.loads(request.cookies.get("flow"))), dict(request.query_params))
else:
result = auth.build_msal_app(cache).acquire_token_by_auth_code_flow(
dict(json.loads(request.cookies.get("flow"))), dict(request.query_params))
if "error" in result:
response = JSONResponse({"status": "error", "result": result})
else:
# response = JSONResponse({"status": "success", "result": result})
response = RedirectResponse("/")
response.set_cookie("user", json.dumps(result.get("id_token_claims")), expires=3600)
except ValueError as e:
response = JSONResponse({"status": "error", "result": "Possible CSRF related error" + str(e)})
return response
# Execute processes the form values entered by the user and generates the TF JSON file
@app.post("/execute")
async def process_input(request: Request):
"""
Process the dynamic form that's posted to this API and perform the required processing
Initiate an async job for executing terraform
:param request: request object sent in the post body when accessing this API
:return: Will return success if all items are completed
"""
dynamic_form = await request.form()
form_values = dict(dynamic_form)
if not request.cookies.get("user") or not request.cookies.get("flow"):
return JSONResponse(content={"status": "Error: User Not Logged In"}, status_code=200)
# Reload form configs:
form_config = find_config()
# Load tfvars initial files
tf_json = find_config(extension=".orig.tfvars.json")
# Create a map based on str maps in the form config files
maps = {}
for _, config_doc in form_config.items():
for _, config in config_doc.items():
if "str_maps" in config:
for strm, smap in config["str_maps"].items():
maps[strm] = form_values[smap]
# Evaluate maps across loaded jsons
for f_name, doc in tf_json.items():
temp_dump = json.dumps(doc)
for strm, smap in maps.items():
temp_dump = temp_dump.replace("{" + strm + "}", smap)
tf_json[f_name] = json.loads(temp_dump)
# Process all form keys:
form_dump = str(json.dumps(form_values))
for key, smap in maps.items():
form_dump = form_dump.replace("{" + key + "}", smap)
form_values = json.loads(form_dump)
# Write the values to the correct locations in the memory loaded json files
for key, value in form_values.items():
if "listinput:" in key:
value = value.split("\n")
key = key.replace("listinput:", "")
for _, doc in tf_json.items():
# Process a list type value
dotted_write(key, value, doc)
# Loop all open TF documents and write them out
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("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")
if keyVaultName:
sp_id = secret_client.get_secret("login-app-clientid").value
sp_pwd = secret_client.get_secret("login-app-clientid").value
else:
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)
with open(exec_output, "w+") as out:
creation = await asyncio.create_subprocess_exec(*config_str.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)
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")
async def poll_results():
"""
Pol results is an async definition used by the /poll API query to return the results of the exec file
:return: will return the current contents of the results text file
"""
try:
with open(exec_output, "r") as res:
return JSONResponse(res.read(), status_code=200)
except:
return JSONResponse({"results": "No content for that job_num"}, status_code=200)
# Primary entry for unvicorn
# TODO: Replace with docker FlaskAPI Base image later
if __name__ == "__main__":
port = 80
if len(sys.argv) > 1:
port = int(sys.argv[1])
uvicorn.run(app, host='0.0.0.0', port=port, debug=True)

Просмотреть файл

@ -0,0 +1,9 @@
uvicorn~=0.12.3
dominate~=2.6.0
fastapi~=0.62.0
python-multipart
aiofiles
azure-keyvault
msal~=1.8.0
starlette~=0.13.6
azure-identity

3872
src/front/static/bootstrap-grid.css поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

7
src/front/static/bootstrap-grid.min.css поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

326
src/front/static/bootstrap-reboot.css поставляемый Normal file
Просмотреть файл

@ -0,0 +1,326 @@
/*!
* Bootstrap Reboot v4.5.3 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus:not(:focus-visible) {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([class]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
-ms-overflow-style: scrollbar;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
[role="button"] {
cursor: pointer;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

8
src/front/static/bootstrap-reboot.min.css поставляемый Normal file
Просмотреть файл

@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.5.3 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

7031
src/front/static/bootstrap.bundle.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

7
src/front/static/bootstrap.bundle.min.js поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

10768
src/front/static/bootstrap.css поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

4418
src/front/static/bootstrap.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

12
src/front/static/bootstrap.min.css поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

7
src/front/static/bootstrap.min.js поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,32 @@
.custom-pane {
background: #252f2f;
border: 1px solid #000000;
border-radius: 15px;
}
.form-elements {
border-bottom: 2px solid #000000;
}
.page-header {
margin-top: 80px;
margin-bottom: 25px;
}
.modal-dialog {
width: 800px;
}
#terminal {
font-family: Terminus,Consolas,Profont,"Andale Mono",Monaco,Inconsolata,Inconsolata-g,
Unifont,Lime,"ClearlyU PUA",Clean,"DejaVu Sans Mono","Lucida Console",
"Bitstream Vera Sans Mono",Freemono,"Liberation Mono",Dina,Anka,Droid Sans Mono,
Anonymous Pro,Proggy fonts,Envy Code R,Gamow,Courier,"Courier New",Terminal,monospace;
position: relative;
font-size: 16px;
padding: 10px;
border-style: solid;
border-width:2px;
border-color: #414141;
background-color: #000000;
width: 765px;
height: 500px;
overflow: auto;
color: #66FF66;
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/* EzDeploy Javascript Cookie Management */
function createCookie(name,value,hours) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(hours*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/";
}
function readCookie(name) {
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(name+"=") == 0) return c.substring(name.length+1,c.length);
}
return null;
}

2
src/front/static/jquery-3.5.1.min.js поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -17,19 +17,19 @@ PGM=$(basename "${0}")
if [[ "$#" -lt 2 ]]; then
echo "apply_terraform.sh: initializes Terraform for a given directory using given a .env file for backend configuration"
echo "usage: apply_terraform.sh <global variables file> <terraform configuration directory> <auto approve (y/n)>"
echo "usage: apply_terraform.sh <global variables file> <terraform configuration directory> <var_file> <auto approve (y/n)>"
exit 1
fi
globalvars=$(realpath "${1}")
tf_dir=$(realpath "${2}")
tf_name=$(basename "${tf_dir}")
tf_name=$(basename "${3}")
config_vars="${tf_dir}/config.vars"
tfvars="${tf_dir}/${tf_name}.tfvars"
tfvars="${tf_dir}/${tf_name}"
auto_approve=${3:-n}
auto_approve=${4:-n}
# check for dependencies
. "${BASH_SOURCE%/*}/util/checkforazcli.sh"
@ -38,14 +38,6 @@ auto_approve=${3:-n}
# Validate necessary Azure resources exist
. "${BASH_SOURCE%/*}/config/config_validate.sh" "${tf_dir}"
# Get the .tfvars file matching the terraform directory name
if [[ ! -f "${tfvars}" ]]
then
echo "${PGM}: Could not find a terraform variables file with the name '${tfvars}' at ${tf_dir}"
echo "${PGM}: Exiting."
exit 1
fi
# Validate configuration file exists
. "${BASH_SOURCE%/*}/util/checkforfile.sh" \
"${config_vars}" \

Просмотреть файл

@ -26,12 +26,15 @@ if [[ "$#" -lt 1 ]]; then
exit 1
fi
mlz_config=$(realpath "${1}")
tf_sub_id_raw=${2:-notset}
tf_name_raw=${3:-notset}
# Front End By Pass Check
if [[ ${1} != "bypass" ]]; then
mlz_config=$(realpath "${1}")
tf_sub_id_raw=${2:-notset}
tf_name_raw=${3:-notset}
# source variables from MLZ config
. "${mlz_config}"
# source variables from MLZ config
. "${mlz_config}"
fi
# remove hyphens for resource naming restrictions
# in the future, do more cleansing
@ -46,14 +49,26 @@ mlz_prefix="mlz-tf"
mlz_sp_name_full="sp-${mlz_prefix}-${mlz_env_name_clean}"
mlz_sa_name_full="mlztfsa${mlz_env_name_clean}${mlz_sub_id_clean}"
mlz_kv_name_full="mlzkv${mlz_env_name_clean}${mlz_sub_id_clean}"
mlz_acr_name_full="mlzacr${mlz_env_name_clean}${mlz_sub_id_clean}"
mlz_fe_app_name_full="mlzfeapp${mlz_env_name_clean}${mlz_sub_id_clean}"
mlz_instance_name_full="mlzfeinstance${mlz_env_name_clean}${mlz_sub_id_clean}"
mlz_dns_name_full="mlzdep${mlz_env_name_clean}${mlz_sub_id_clean}"
# Name MLZ config resources
export mlz_rg_name="rg-${mlz_prefix}-${mlz_env_name_clean}"
export mlz_sp_name="${mlz_sp_name_full}"
export mlz_sp_kv_name="${mlz_sp_name_full}-clientid"
export mlz_sp_kv_password="${mlz_sp_name_full}-pwd"
export mlz_sp_kv_name="serviceprincipal-clientid"
export mlz_sp_kv_password="serviceprincipal-pwd"
export mlz_login_app_kv_name="login-app-clientid"
export mlz_login_app_kv_password="login-app-pwd"
export mlz_sa_name="${mlz_sa_name_full:0:24}" # take the 24 characters of the storage account name
export mlz_kv_name="${mlz_kv_name_full:0:24}" # take the 24 characters of the key vault name
export mlz_acr_name="${mlz_acr_name_full:0:24}"
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

0
src/scripts/config/get_sp_identity.sh Normal file → Executable file
Просмотреть файл

Просмотреть файл

@ -25,23 +25,29 @@ if [[ "$#" -lt 1 ]]; then
exit 1
fi
mlz_tf_cfg=$(realpath "${1}")
# Front End By Pass Check
if [[ ${1} != "bypass" ]]; then
# Source variables
. "${mlz_tf_cfg}"
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
# generate MLZ configuration names
. "${BASH_SOURCE%/*}/generate_names.sh" "${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=()
for mlz_sub in $mlz_subs
do
# Grab value of variable
@ -52,6 +58,7 @@ do
done
# Create Azure AD application registration and Service Principal
# TODO: Lift the subscription scoping out of here and move into conditional
echo "Verifying Service Principal is unique (${mlz_sp_name})"
if [[ -z $(az ad sp list --filter "displayName eq '${mlz_sp_name}'" --query "[].displayName" -o tsv) ]];then
echo "Service Principal does not exist...creating"
@ -68,12 +75,15 @@ if [[ -z $(az ad sp list --filter "displayName eq '${mlz_sp_name}'" --query "[].
--query appId \
--output tsv)
# Get Service Principal ObjectId
# Get Service Principal ObjectId
sp_objid=$(az ad sp show \
--id "http://${mlz_sp_name}" \
--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

Просмотреть файл

@ -0,0 +1,13 @@
[
{
"x-ms-resourceAppName": "Microsoft Graph",
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "b340eb25-3456-403f-be2f-af7a0d370277",
"type": "Scope",
"x-ms-name": "User.ReadBasic.All"
}
]
}
]

Просмотреть файл

@ -17,19 +17,19 @@ PGM=$(basename "${0}")
if [[ "$#" -lt 2 ]]; then
echo "destroy_terraform.sh: initializes Terraform for a given directory using given a .env file for backend configuration"
echo "usage: destroy_terraform.sh <global variables file> <terraform configuration directory> <auto approve (y/n)>"
echo "usage: destroy_terraform.sh <global variables file> <terraform configuration directory> <var_file> <auto approve (y/n)>"
exit 1
fi
globalvars=$(realpath "${1}")
tf_dir=$(realpath "${2}")
tf_name=$(basename "${tf_dir}")
tf_name=$(basename "${3}")
config_vars="${tf_dir}/config.vars"
tfvars="${tf_dir}/${tf_name}.tfvars"
tfvars="${tf_dir}/${tf_name}"
auto_approve=${3:-n}
auto_approve=${4:-n}
# check for dependencies
. "${BASH_SOURCE%/*}/util/checkforazcli.sh"
@ -38,14 +38,6 @@ auto_approve=${3:-n}
# Validate necessary Azure resources exist
. "${BASH_SOURCE%/*}/config/config_validate.sh" "${tf_dir}"
# Get the .tfvars file matching the terraform directory name
if [[ ! -f "${tfvars}" ]]
then
echo "${PGM}: Could not find a terraform variables file with the name '${tfvars}' at ${tf_dir}"
echo "${PGM}: Exiting."
exit 1
fi
# Validate configuration file exists
. "${BASH_SOURCE%/*}/util/checkforfile.sh" \
"${config_vars}" \

45
src/scripts/ezdeploy_docker.sh Executable file
Просмотреть файл

@ -0,0 +1,45 @@
#!/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 <load|build> {{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

Просмотреть файл

@ -17,8 +17,8 @@ error_log() {
}
usage() {
echo "mlz_tf_setup.sh: configure a resource group that contains Terraform state and a secret store"
error_log "usage: mlz_tf_setup.sh <mlz tf config vars>"
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 <mlz tf config vars> <bypass>"
}
if [[ "$#" -lt 1 ]]; then
@ -49,8 +49,9 @@ create_tf_config() {
##################################################
# generate MLZ configuration resources
. "${BASH_SOURCE%/*}/config/mlz_config_create.sh" "${mlz_tf_cfg}" "${mlz_env_name}" "${mlz_config_location}"
if [[ ${2} != "bypass" ]]; then
. "${BASH_SOURCE%/*}/config/mlz_config_create.sh" "${mlz_tf_cfg}" "${mlz_env_name}" "${mlz_config_location}"
fi
create_tf_config "${mlz_saca_subid}" "${core_path}/saca-hub"
create_tf_config "${mlz_tier0_subid}" "${core_path}/tier-0"

Просмотреть файл

@ -0,0 +1,17 @@
#!/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 dumps the docker image and compresses it for transport to a target network
# Run Docker Setup Script
docker build -t lzfront "${BASH_SOURCE%/*}/../"
docker save -o mlz.tar lzfront:latest
zip mlz.zip mlz.tar
rm mlz.tar

206
src/scripts/setup_ezdeploy.sh Executable file
Просмотреть файл

@ -0,0 +1,206 @@
#!/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.
# SC2154: "var is referenced but not assigned". These values come from an external file.
#
# This script deploys container registries, app registrations, and a container instance to run the MLZ front end
set -e
error_log() {
echo "${1}" 1>&2;
}
usage() {
echo "setup_ezdeploy.sh: Setup the Front End for MLZ"
error_log "usage: setup_ezdeploy.sh -d <local|build|load> -s <subscription_id> -t <tenant_id> -l <location> -e <tf_env_name {{default=public}}> -m <mlz_env_name {{default=mlzdeployment}}> -p <web_port {{default=80}}> -0 <saca_subscription_id> -1 <tier0_subscription_id> -2 <tier1_subscription_id> -3 <tier2_subscription_id>"
}
if [[ "$#" -lt 8 ]]; then
usage
exit 1
fi
export tf_environment=public
export mlz_env_name=mlzdeployment
export web_port=80
subs=()
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}
subs+=("${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}
subs+=("${OPTARG}")
;;
1) export mlz_tier0_subid=${OPTARG}
subs+=("${OPTARG}")
;;
2) export mlz_tier1_subid=${OPTARG}
subs+=("${OPTARG}")
;;
3) export mlz_tier2_subid=${OPTARG}
subs+=("${OPTARG}")
;;
?)
echo "Invalid option: -${OPTARG}."
exit 2
;;
esac
done
# 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}"
# Handle Deployment of Login Services
# 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
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
. "${BASH_SOURCE%/*}/ezdeploy_docker.sh" "$docker_strategy"
docker tag lzfront:latest "${mlz_acr_name}.azurecr.io/lzfront:latest"
echo "INFO: Logging into Container Registry"
az acr login --name "${mlz_acr_name}"
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
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)
echo "INFO: creating instance"
fqdn=$(az container create \
--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}" 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
# 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

0
src/scripts/util/checkforfile.sh Normal file → Executable file
Просмотреть файл