Skip to content

Instantly share code, notes, and snippets.

@brentmcconnell
Last active July 28, 2021 08:25
Show Gist options
  • Save brentmcconnell/d1bb14d31ab69578c5d9ef816015ddda to your computer and use it in GitHub Desktop.
Save brentmcconnell/d1bb14d31ab69578c5d9ef816015ddda to your computer and use it in GitHub Desktop.
Setup RG and SP for a project and optionally create a provider.tf file for use by Terraform
#!/bin/bash
# This script bootstraps a resource group in Azure with a Service Principal
# that has Owner on the resource group. It will also create a Storage Account
# and Keyvault in the resource group and load the SP values into the Keyvault.
# You can optionally create a provider.tf file that references a tfstate
# container for use with Terraform.
# This can be used without any parameters and names will be generated or you
# can optionally pass in a (-g) resource group, (-r) region.
# You can also pass environment variables for configuration. The following are
# accepted variables:
# RESOURCE_GROUP : Name of resource group to create
# REGION : Region resource will be created in
# PROFILER_FILE : Name of file to create for Terraform
# PREFIX : PREFIX to use with Azure resources for uniqueness
# STORAGE_ACCT_NAME : Name of storage account (must be unique across Azure)
# CONTAINER_NAME : Name of container in storage acct used for Terraform state
# KEYVAULT_NAME : Name of KeyVault in Azure (must be unique across Azure)
# SP_NAME : Name of Service Principal to create
set -o errexit # exit if any statement returns a non-true return value
shopt -s expand_aliases
# 3 digit random number
if [ -z $PREFIX ]; then
RND=$(echo $RANDOM | grep -o ....$)
else
RND=$PREFIX
fi
URND=$(echo $RND | tr "[:lower:]" "[:upper:]")
LRND=$(echo $RND | tr "[:upper:]" "[:lower:]")
echo "Check program requirements..."
(
set +e
programs=(az jq)
missing=0
for i in ${programs[@]}; do
command -v $i 2&> /dev/null
if [ $? -eq 0 ]; then
echo " * Found $i"
else
echo " * ERROR: missing $i"
missing=1
fi
done
if [[ "$missing" -ne 0 ]]; then
echo "Missing required commands"
exit 1
fi
)
usage() {
echo "`basename $0`"
echo " Usage: "
echo " [-g <project resource group> project resource group to create"
echo " [-r <region>] region to use. EastUs or USGovVirginia are defaults"
exit 1
}
# Catch any help requests
for arg in "$@"; do
case "$arg" in
--help| -h)
usage
;;
esac
done
while getopts f:r:g: option
do
case "${option}"
in
g) RESOURCE_GROUP=${OPTARG};;
r) REGION=${OPTARG};;
f) PROVIDER_FILE=${OPTARG};;
*) usage;;
: ) usage;;
esac
done
shift "$(($OPTIND -1))"
# Check if Region is passed in, otherwise eastus will be used
if [ -z "$REGION" ]; then
# check if in gov or commercial
CLOUD=`az account list-locations -o json | jq -r '.[0].name'`
if [ ${CLOUD:0:5} = "usgov" ]; then
REGION='usgovvirginia'
else
REGION='eastus'
fi
fi
if [ -z "$RESOURCE_GROUP" ]; then
RESOURCE_GROUP=PROJECT-$URND-RG
fi
if [ -z "$STORAGE_ACCT_NAME" ]; then
STORAGE_ACCT_NAME=terraformstate${LRND}sa
fi
if [ -z "$CONTAINER_NAME" ]; then
CONTAINER_NAME=terraformstate
fi
if [ -z "$KEYVAULT_NAME" ]; then
KEYVAULT_NAME=project-secrets-$LRND-kv
fi
if [ -z "$SP_NAME" ]; then
SP_NAME=project-$LRND-sp
fi
echo "The following resources will be created...\n"
echo "RESOURCE_GROUP: $RESOURCE_GROUP"
echo "STORAGE_ACCT_NAME: $STORAGE_ACCT_NAME"
echo "CONTAINER_NAME: $CONTAINER_NAME"
echo "KEYVAULT_NAME: $KEYVAULT_NAME"
echo "SP: $SP_NAME"
echo "REGION: $REGION"
echo "PROVIDER_FILE: $PROVIDER_FILE"
echo -e "NOTE: Service Principal with Owner permissions on $RESOURCE_GROUP stored in $KEYVAULT_NAME\n"
read -p "Are you sure you want to Proceed [y/N]?"
if ! [[ "$REPLY" =~ ^[Yy]$ ]]; then
echo "Maybe next time!"
exit 1
fi
alias echo="echo -e"
# Create resource group
RG_ID=$(az group create --name $RESOURCE_GROUP --location $REGION --query id -o tsv)
# Create storage account
az storage account create --resource-group $RESOURCE_GROUP --name $STORAGE_ACCT_NAME --sku Standard_LRS --encryption-services blob 1>/dev/null
# Get storage account key
ACCOUNT_KEY=$(az storage account keys list --resource-group $RESOURCE_GROUP --account-name $STORAGE_ACCT_NAME --query [0].value -o tsv)
# Create blob container
az storage container create --name $CONTAINER_NAME --account-name $STORAGE_ACCT_NAME --account-key $ACCOUNT_KEY 1>/dev/null
echo "storage_account_name: $STORAGE_ACCT_NAME"
echo "container_name: $CONTAINER_NAME"
# Create Keyvault if it doesn't exist. This keyvault will hold service principal info
# for our projects.
(
set +e
KEYVAULT=$(az keyvault show -n $KEYVAULT_NAME -g $RESOURCE_GROUP -o json)
if [ $? -ne 0 ]; then
KEYVAULT=$(az keyvault create -n $KEYVAULT_NAME -g $RESOURCE_GROUP -o json)
fi
)
###############################
## Create Project Resources
###############################
# Create Service Principal
echo "Create or retrieve Service Principal"
SP_TOKEN=$(az ad sp create-for-rbac --role Owner -n $SP_NAME --scopes $RG_ID -o json)
# Grab info from the Service Principal
SP_APPID=$(echo $SP_TOKEN | jq -e -r 'select(.appId != null) | .appId')
SP_TENANTID=$(echo $SP_TOKEN | jq -e -r 'select(.tenant != null) | .tenant')
SP_PASSWORD=$(echo $SP_TOKEN | jq -e -r 'select(.password != null) | .password')
SP_OBJID=$(az ad sp show --id $SP_APPID | jq -e -r 'select(.objectId != null) | .objectId')
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
# Wait for Progagation via loop
echo "Sleeping in loop for 5 seconds to propagate SP"
until az ad sp show --id $SP_APPID &> /dev/null ; do echo "Waiting for Azure AD propagation" && sleep 5; done
# Add SP info and access key to keyvault
az keyvault secret set --vault-name $KEYVAULT_NAME --name "SA-ACCESS-KEY" --value "$ACCOUNT_KEY"
az keyvault secret set --vault-name $KEYVAULT_NAME --name "SP-CLIENTID" --value "$SP_APPID"
az keyvault secret set --vault-name $KEYVAULT_NAME --name "SP-SUBSCRIPTIONID" --value "$SUBSCRIPTION_ID"
az keyvault secret set --vault-name $KEYVAULT_NAME --name "SP-TENANTID" --value "$SP_TENANTID"
az keyvault secret set --vault-name $KEYVAULT_NAME --name "SP-PASSWORD" --value "$SP_PASSWORD"
# Allow SP to manage Keyvault
az keyvault set-policy --name $KEYVAULT_NAME --object-id $SP_OBJID \
--certificate-permissions backup create delete deleteissuers get getissuers import list listissuers managecontacts manageissuers purge recover restore setissuers update \
--key-permissions backup create decrypt delete encrypt get import list purge recover restore sign unwrapKey update verify wrapKey \
--secret-permissions backup delete get list purge recover restore set \
--storage-permissions backup delete deletesas get getsas list listsas purge recover regeneratekey restore set setsas update
if ! [ -z $PROVIDER_FILE ]; then
nl='\n'; tab='\t'
# Get the latest release of azurerm provider
AZURE_RM_LATEST=$(curl --silent "https://api.github.com/repos/terraform-providers/terraform-provider-azurerm/releases/latest" |
grep '"tag_name":' |
sed -E 's/.*"([^"]+)".*/\1/' |
sed -E 's/^v/~>/'
)
if [ -z AZURE_RM_LATEST ]; then
AZURE_RM_LATEST="2.50.0"
fi
OUTPUTS=$(cat <<-EOF
terraform { $nl
${tab}required_providers { $nl
${tab}${tab}azurerm = { $nl
${tab}${tab}${tab}source = "hashicorp/azurerm" $nl
${tab}${tab}${tab}version = "$AZURE_RM_LATEST" $nl
${tab}${tab}} $nl
${tab}} $nl
${tab}backend "azurerm" { $nl
${tab}${tab}storage_account_name = "$STORAGE_ACCT_NAME" $nl
${tab}${tab}container_name = "$CONTAINER_NAME" $nl
${tab}${tab}key = "terraform.state.main" $nl
${tab}} $nl
} $nl
$nl
provider "azurerm" { $nl
${tab}skip_provider_registration = true $nl
${tab}features {} $nl
} $nl
EOF
)
#Output file to location
echo $OUTPUTS > $PROVIDER_FILE
fi
echo "THE END"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment