diff --git a/AlwaysEncryptedSample.sln b/AlwaysEncryptedSample.sln index 3911587..515806f 100644 --- a/AlwaysEncryptedSample.sln +++ b/AlwaysEncryptedSample.sln @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .gitattributes = .gitattributes .gitignore = .gitignore + appveyor.yml = appveyor.yml Invoke-EncryptColumns.ps1 = Invoke-EncryptColumns.ps1 License.md = License.md New-EncryptionKeys.ps1 = New-EncryptionKeys.ps1 diff --git a/appveyor.yml b/appveyor.yml index d21f4df..e50a2c7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -29,7 +29,7 @@ environment: SQL_SERVER_COLUMN_CERTIFICATE: AlwaysEncryptedSampleCMK matrix: - {} -services: +services: - mssql2016 nuget: # This might be causing a hang. diff --git a/terraform/.gitignore b/terraform/.gitignore new file mode 100644 index 0000000..5590f2b --- /dev/null +++ b/terraform/.gitignore @@ -0,0 +1,34 @@ +# Created by https://www.gitignore.io/api/terraform +# Edit at https://www.gitignore.io/?templates=terraform + +### Terraform ### +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +# +# example.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# End of https://www.gitignore.io/api/terraform diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..739ce9a --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,34 @@ +# Terraform support (experimental) + +## Overview + +This folder contains a terraform file to create an Azure Resource Group with all the necessary infrastructure to deploy the AlwaysEncryptedSample app to (in theory). + +All thise commands assume you are in the `/terraform/` folder in the git repo. The best way to ensure you are there (assuming your terminals working directory is anywhere in the git repo) is `Join-Path -Path "$(git rev-parse --show-toplevel)" -childpath 'terraform' | Set-Location` + +## Howto + +### Creating the resource group + +```powershell +az login +az account set --subscription='SUBSCRIPTION_ID_I_WANT_TO_USE' +$env:TF_VAR_certificate_creator = $(az ad signed-in-user show --query objectId --otsv) +terraform init +terraform plan +terraform apply +``` + +### Cleaning up + +If you don't want to rack of charges then the command is `az group delete --name AlwaysEncryptedSample`. You are also going to want to delete your terraform state (i.e. the `terraform.tfstate` file, hereafter referred to as tfstate) after deleting the reource grouns as your next `terraform apply` will fail otherwise. The tfstate associates the terraform objects in your `.tf` files with the guid identifiers of the azure resources. I haven't looked to hard into the details, but the script as is can't create everything from scratch if there is an existing tfstate. Therefore you probably want to do the following: + +```powershell +az group delete --name AlwaysEncryptedSample +Remove-Item -Path .\terraform.tfstate +Remove-Item -Path .\terraform.tfstate.backup +``` + +## Further directions + +* I'd like t0 store the state in Azure cloud storage and just be smarter about things. diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..ac1ec1f --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,204 @@ +data "azurerm_client_config" "current" { + +} + +output "tenant_id" { + value = "${data.azurerm_client_config.current.tenant_id}" +} + +variable "resource_location" { + type = "string" + default = "East US" +} + +variable "certificate_creator" { + type = "string" + default = "" +} + +variable "resource_names" { + type = "map" + default = { + "ApplicationInsights" = "AlwaysEncryptedSample" + "AppServicePlan" = "always-encrypted-sample-appserviceplan" + "ResourceGroup" = "AlwaysEncryptedSample" + "SqlServer" = "alwaysencryptedsample" + "SqlDatabase" = "AlwaysEncryptedSample" + "AppService" = "AlwaysEncryptedSampleWeb3" + "KeyVault" = "AlwaysEncryptedSampleKeyVault" + "ColumnCertificate" = "ColumnCertificate" + } + +} +variable "certificate_cn" { + type = "string" + default = "CN=Always Encrypted Sample Cert" +} + +variable "sql_settings" { + type = "map" + default = { + "admin_login" = "essay" + "admin_password" = "lbDG62XZy6i3pL8aC%Lw%uY7RYLN8o3aG2XhaH8dM2wbu0NPCMo0R" + } +} + +resource "azurerm_resource_group" "always_encrypted_sample" { + name = "${var.resource_names["ResourceGroup"]}" + location = "${var.resource_location}" +} + +resource "azurerm_app_service_plan" "always_encrypted_sample" { + name = "${var.resource_names["AppServicePlan"]}" + location = "${azurerm_resource_group.always_encrypted_sample.location}" + resource_group_name = "${azurerm_resource_group.always_encrypted_sample.name}" + kind = "app" + sku { + tier = "Free" + size = "F1" + } +} + +resource "azurerm_application_insights" "app_insights" { + name = "${var.resource_names["ApplicationInsights"]}" + resource_group_name = "${azurerm_resource_group.always_encrypted_sample.name}" + location = "${azurerm_resource_group.always_encrypted_sample.location}" + application_type = "Web" +} + +output "instrumentation_key" { + value = "${azurerm_application_insights.app_insights.instrumentation_key}" +} + + +resource "azurerm_sql_server" "sql_server" { + name = "${var.resource_names["SqlServer"]}" + resource_group_name = "${azurerm_resource_group.always_encrypted_sample.name}" + location = "${azurerm_resource_group.always_encrypted_sample.location}" + version = "12.0" + administrator_login = "${var.sql_settings["admin_login"]}" + administrator_login_password = "${var.sql_settings["admin_password"]}" + lifecycle { + ignore_changes = ["administrator_login_password"] + } +} + +resource "azurerm_sql_database" "sql_database" { + name = "${var.resource_names["SqlDatabase"]}" + resource_group_name = "${azurerm_resource_group.always_encrypted_sample.*.name[0]}" + location = "${azurerm_resource_group.always_encrypted_sample.*.location[0]}" + server_name = "${azurerm_sql_server.sql_server.*.name[0]}" + edition = "Standard" + create_mode = "Default" + requested_service_objective_name = "S0" +} + + +resource "azurerm_app_service" "web_3" { + name = "${var.resource_names["AppService"]}" + resource_group_name = "${azurerm_resource_group.always_encrypted_sample.*.name[0]}" + app_service_plan_id = "${azurerm_app_service_plan.always_encrypted_sample.id}" + location = "${azurerm_resource_group.always_encrypted_sample.*.location[0]}" + https_only = "true" + app_settings = { + APPINSIGHTS_INSTRUMENTATIONKEY = "${azurerm_application_insights.app_insights.instrumentation_key}" + } + site_config { + default_documents = [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html", + ] + http2_enabled = false //TODO: figure out if enabling this helps anything + ftps_state = "Disabled" + use_32_bit_worker_process = true + } +} + +/* +output "web_3_service_principle_id" { + value = "${azurerm_app_service.web_3.identity.0.principal_id}" +} +*/ + + +resource "azurerm_key_vault" "always_encrypted_sample" { + name = "${azurerm_resource_group.always_encrypted_sample.*.name[0]}" + resource_group_name = "${var.resource_names["ResourceGroup"]}" + location = "${azurerm_resource_group.always_encrypted_sample.*.location[0]}" + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + sku { + name = "standard" + } + + access_policy { + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${var.certificate_creator}" + + certificate_permissions = [ + "create", "get" # Terraform needs get to make the cert, probably to check its existance + ] + } +} + +output "key_vault_uri" { + value = "${azurerm_key_vault.always_encrypted_sample.vault_uri}" +} + +resource "azurerm_key_vault_certificate" "column_certificate" { + name = "${var.resource_names["ColumnCertificate"]}" + key_vault_id = "${azurerm_key_vault.always_encrypted_sample.id}" + + certificate_policy { + issuer_parameters { + name = "Self" + } + + key_properties { + exportable = false + key_size = 4096 + key_type = "RSA" + reuse_key = true #TODO: Can I make this false? + } + + #TODO We might want to auto renew if we are crazy. + /* + lifetime_action { + action { + action_type = "AutoRenew" + } + + trigger { + days_before_expiry = 30 + } + } + */ + secret_properties { + content_type = "application/x-pkcs12" + } + + x509_certificate_properties { + extended_key_usage = [ + "1.3.6.1.5.5.8.2.2", + "1.3.6.1.4.1.311.10.3.1" + ] + + key_usage = [ + "dataEncipherment", + ] + + subject_alternative_names { + dns_names = ["${azurerm_sql_server.sql_server.fully_qualified_domain_name}"] + } + + subject = "${var.certificate_cn}" + validity_in_months = 12 + } + } +}