Merge pull request #29 from pkgw/cx-launch

Roll out Constellations to production deployment
This commit is contained in:
Peter Williams 2023-10-25 08:45:42 -04:00 коммит произвёл GitHub
Родитель 12faf0f27e 48db673e5a
Коммит 4cca3305ed
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 1042 добавлений и 3 удалений

2
prod/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
bastion_rsa
bastion_rsa.pub

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

@ -69,8 +69,21 @@ resource "azurerm_cdn_endpoint_custom_domain" "web" {
lifecycle {
prevent_destroy = true
}
depends_on = [
azurerm_dns_cname_record.assets_web,
]
}
resource "azurerm_dns_cname_record" "assets_web" {
name = "web"
resource_group_name = azurerm_dns_zone.assets.resource_group_name
zone_name = azurerm_dns_zone.assets.name
ttl = 3600
target_resource_id = azurerm_cdn_endpoint.web.id
}
# data1.wwtassets.org
resource "azurerm_cdn_endpoint" "data1" {
@ -108,6 +121,89 @@ resource "azurerm_cdn_endpoint_custom_domain" "data1" {
lifecycle {
prevent_destroy = true
}
depends_on = [
azurerm_dns_cname_record.assets_data1,
]
}
resource "azurerm_dns_cname_record" "assets_data1" {
name = "data1"
resource_group_name = azurerm_dns_zone.assets.resource_group_name
zone_name = azurerm_dns_zone.assets.name
ttl = 3600
target_resource_id = azurerm_cdn_endpoint.data1.id
}
# cx.wwtassets.org
resource "azurerm_cdn_endpoint" "cxdata" {
name = "${var.prefix}-cxdata"
profile_name = azurerm_cdn_profile.main.name
resource_group_name = azurerm_resource_group.web_frontend_legacy.name
location = "global"
optimization_type = "GeneralWebDelivery"
origin_host_header = azurerm_storage_account.constellations.primary_blob_host
querystring_caching_behaviour = "UseQueryString"
origin {
name = "constellations"
host_name = azurerm_storage_account.constellations.primary_blob_host
}
global_delivery_rule {
modify_response_header_action {
action = "Overwrite"
name = "Access-Control-Allow-Origin"
value = "*"
}
modify_response_header_action {
action = "Overwrite"
name = "Access-Control-Allow-Methods"
value = "GET"
}
modify_response_header_action {
action = "Overwrite"
name = "Access-Control-Allow-Headers"
value = "Content-Disposition,Content-Encoding,Content-Type"
}
}
lifecycle {
prevent_destroy = true
}
}
resource "azurerm_cdn_endpoint_custom_domain" "cxdata" {
name = "${var.prefix}-cxdata"
# Capitalization consistency issue:
cdn_endpoint_id = replace(azurerm_cdn_endpoint.cxdata.id, "resourcegroups", "resourceGroups")
host_name = "cx.wwtassets.org"
cdn_managed_https {
certificate_type = "Dedicated"
protocol_type = "ServerNameIndication"
tls_version = "TLS12"
}
lifecycle {
prevent_destroy = true
}
depends_on = [
azurerm_dns_cname_record.assets_cx,
]
}
resource "azurerm_dns_cname_record" "assets_cx" {
name = "cx"
resource_group_name = azurerm_dns_zone.assets.resource_group_name
zone_name = azurerm_dns_zone.assets.name
ttl = 3600
target_resource_id = azurerm_cdn_endpoint.cxdata.id
}
# docs.worldwidetelescope.org

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

@ -0,0 +1,56 @@
# Base-layer infrastructure for the Constellations backend services.
#
# Because the MongoDB is isolated on a private network, the usual Azure admin
# systems do not work. However, with the bastion host setup defined in
# `constellations-bastion.tf`, it is possible to administer the database
# locally.
#
# 1. First, set up the bastion and SSH into it.
# 2. Forward a port to the DB:
# ```
# ssh -O forward -L 10255:wwtprod-cxbe-server.mongo.cosmos.azure.com:10255 wwt@wwtprodcxb.westus.cloudapp.azure.com
# ```
# 3. Make a temporary connection string, replacing the `...cosmos.azure.com` hostname
# with `localhost`. You can get the connection string from the database's admin
# page in the Azure Portal.
# 4. Connect using pymongo with some special settings:
# ```
# conn = pymongo.MongoClient(cs, tlsAllowInvalidCertificates=True, directConnection=True)
# ```
# where `cs` is the temporary connection string.
resource "azurerm_resource_group" "cx_backend" {
name = "${var.prefix}-cxbackend"
location = var.location
lifecycle {
prevent_destroy = true
}
}
# App service plan for the backend services. The hope is that these will scale
# more or less in unison ...
resource "azurerm_service_plan" "cx_backend" {
name = "${var.prefix}cxbackend"
resource_group_name = azurerm_resource_group.cx_backend.name
location = azurerm_resource_group.cx_backend.location
os_type = "Linux"
sku_name = "P1v2"
}
# The backend virtual network
resource "azurerm_virtual_network" "cx_backend" {
name = "${var.prefix}-cxbeVnet"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
address_space = ["10.0.0.0/16"]
}
resource "azurerm_subnet" "cx_backend_main" {
name = "${var.prefix}-cxbeSubnet"
resource_group_name = azurerm_resource_group.cx_backend.name
virtual_network_name = azurerm_virtual_network.cx_backend.name
address_prefixes = ["10.0.0.0/24"]
}

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

@ -0,0 +1,118 @@
# The Constellations backend app service
resource "azurerm_linux_web_app" "cx_backend" {
name = "${var.prefix}-cxbe"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
service_plan_id = azurerm_service_plan.cx_backend.id
app_settings = {
"AZURE_COSMOS_CONNECTIONSTRING" = azurerm_cosmosdb_account.cx_backend.connection_strings[0]
"CX_PREVIEW_BASE_URL" = "https://${azurerm_cdn_endpoint_custom_domain.cxdata.host_name}/previews"
"CX_PREVIEW_SERVICE_URL" = "http://${azurerm_private_dns_a_record.cx_previewer_server.name}.azurewebsites.net"
"CX_SESSION_SECRETS" = var.sessionSecrets
"CX_SUPERUSER_ACCOUNT_ID" = var.superuserAccountId
"KEYCLOAK_URL" = "https://${var.tld}/auth/"
}
site_config {
always_on = true
ftps_state = "FtpsOnly"
vnet_route_all_enabled = true
app_command_line = "yarn start"
}
logs {
detailed_error_messages = false
failed_request_tracing = false
http_logs {
file_system {
retention_in_days = 0
retention_in_mb = 35
}
}
}
virtual_network_subnet_id = azurerm_subnet.cx_backend_app.id
}
# "stage" slot identical but not always_on. Note that most config/settings
# swap when you swap deployment slots, so this slot and production must
# be kept in sync. Fortunately the always_on setting stays put.
resource "azurerm_linux_web_app_slot" "cx_backend_stage" {
name = "stage"
app_service_id = azurerm_linux_web_app.cx_backend.id
app_settings = azurerm_linux_web_app.cx_backend.app_settings
site_config {
always_on = false
ftps_state = "FtpsOnly"
vnet_route_all_enabled = true
app_command_line = "yarn start"
}
logs {
detailed_error_messages = false
failed_request_tracing = false
http_logs {
file_system {
retention_in_days = 0
retention_in_mb = 35
}
}
}
virtual_network_subnet_id = azurerm_linux_web_app.cx_backend.virtual_network_subnet_id
}
# Public custom hostname for the backend app
resource "azurerm_dns_cname_record" "api" {
name = "api"
resource_group_name = azurerm_dns_zone.flagship.resource_group_name
zone_name = azurerm_dns_zone.flagship.name
ttl = 3600
record = "${azurerm_linux_web_app.cx_backend.default_hostname}."
}
resource "azurerm_app_service_custom_hostname_binding" "cx_backend" {
hostname = "api.${var.tld}"
resource_group_name = azurerm_resource_group.cx_backend.name
app_service_name = azurerm_linux_web_app.cx_backend.name
# These are managed through the cert binding:
lifecycle {
ignore_changes = [ssl_state, thumbprint]
}
}
resource "azurerm_app_service_managed_certificate" "cx_backend" {
custom_hostname_binding_id = azurerm_app_service_custom_hostname_binding.cx_backend.id
}
resource "azurerm_app_service_certificate_binding" "cx_backend" {
hostname_binding_id = azurerm_app_service_custom_hostname_binding.cx_backend.id
certificate_id = azurerm_app_service_managed_certificate.cx_backend.id
ssl_state = "SniEnabled"
}
# Let the app talk with the support services
resource "azurerm_subnet" "cx_backend_app" {
name = "${var.prefix}-cxbeAppSubnet"
resource_group_name = azurerm_resource_group.cx_backend.name
virtual_network_name = azurerm_virtual_network.cx_backend.name
address_prefixes = ["10.0.2.0/24"]
delegation {
name = "dlg-appServices"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
}

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

@ -0,0 +1,102 @@
# A bastion host for the Constellations VPN so that we can see if things are
# actually working.
#
# To create the bastion, uncomment everything and generate an SSH key:
#
# `ssh-keygen -t rsa -b 4096 -C "wwt@cxbastion" -f bastion_rsa`
#
# Connect with:
#
# ```
# ssh -oIdentitiesOnly=yes -oPubkeyAcceptedAlgorithms=+ssh-rsa
# -i bastion_rsa wwt@wwtprodcxb.westus.cloudapp.azure.com
# ```
#
# Then see, e.g. the header comment in `constellations-previewer.tf` for some
# hints about how to admin the previewer.
resource "azurerm_subnet" "cx_bastion" {
name = "${var.prefix}-cxbastion"
resource_group_name = azurerm_resource_group.cx_backend.name
virtual_network_name = azurerm_virtual_network.cx_backend.name
address_prefixes = ["10.0.220.0/24"]
}
resource "azurerm_public_ip" "cx_bastion" {
name = "${var.prefix}-cxbastion"
resource_group_name = azurerm_resource_group.cx_backend.name
location = azurerm_resource_group.cx_backend.location
allocation_method = "Static"
domain_name_label = "${var.prefix}cxb"
lifecycle {
create_before_destroy = true
}
}
resource "azurerm_network_interface" "cx_bastion" {
name = "${var.prefix}-cxbastion"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.cx_bastion.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.cx_bastion.id
}
}
resource "azurerm_linux_virtual_machine" "cx_bastion" {
name = "${var.prefix}-cxbastion"
computer_name = "cxbastion"
resource_group_name = azurerm_resource_group.cx_backend.name
location = azurerm_resource_group.cx_backend.location
size = "Standard_B1ls"
admin_username = "wwt"
network_interface_ids = [
azurerm_network_interface.cx_bastion.id
]
admin_ssh_key {
username = "wwt"
public_key = file("bastion_rsa.pub")
}
os_disk {
name = "osdisk"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "RedHat"
offer = "RHEL"
sku = "83-gen2"
version = "latest"
}
}
resource "azurerm_network_security_group" "cx_bastion" {
name = "${var.prefix}-cxbastion"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
security_rule {
name = "SSH"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
resource "azurerm_network_interface_security_group_association" "cx_bastion" {
network_interface_id = azurerm_network_interface.cx_bastion.id
network_security_group_id = azurerm_network_security_group.cx_bastion.id
}

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

@ -0,0 +1,103 @@
# The CosmosDB (MongoDB-alike) for the Constellations framework.
#
# Because the database is isolated on a private network, the usual Azure admin
# systems do not work. However, with the bastion host setup defined in
# `constellations-bastion.tf`, it is possible to administer the database
# locally.
#
# 1. First, set up the bastion and SSH into it.
# 2. Forward a port to the DB:
# ```
# ssh -O forward -L 10255:wwtprod-cxbe-nosql.mongo.cosmos.azure.com:10255 wwt@wwtprodcxb.westus.cloudapp.azure.com
# ```
# 3. Make a temporary connection string, replacing the `...cosmos.azure.com`
# hostname with `localhost`. You can get the connection string from the
# database's admin page in the Azure Portal.
# 4. Connect using pymongo with some special settings:
# ```
# conn = pymongo.MongoClient(cs, tlsAllowInvalidCertificates=True, directConnection=True)
# ```
# where `cs` is the temporary connection string.
resource "azurerm_cosmosdb_account" "cx_backend" {
name = "${var.prefix}-cxbe-nosql"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
offer_type = "Standard"
kind = "MongoDB"
geo_location {
location = azurerm_resource_group.cx_backend.location
failover_priority = 0
}
consistency_policy {
consistency_level = "Session"
max_interval_in_seconds = 5
max_staleness_prefix = 100
}
restore {
restore_timestamp_in_utc = "2023-10-21T21:00:00Z"
source_cosmosdb_account_id = "/subscriptions/581389a3-e46e-43a4-bef4-4d0c1c43e6a6/providers/Microsoft.DocumentDB/locations/westus/restorableDatabaseAccounts/56fc946b-0a0b-4674-9f05-0f2b8cd73b69"
}
}
# Supporting vnet/private-endpoint stuff
resource "azurerm_private_dns_zone" "cx_backend" {
name = "privatelink.mongo.cosmos.azure.com"
resource_group_name = azurerm_resource_group.cx_backend.name
}
resource "azurerm_private_endpoint" "cx_backend" {
name = "${var.prefix}-cxbeDbEndpoint"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
subnet_id = azurerm_subnet.cx_backend_main.id
private_dns_zone_group {
name = "default"
private_dns_zone_ids = [azurerm_private_dns_zone.cx_backend.id]
}
private_service_connection {
name = "${var.prefix}-cxbeDbEndpoint"
private_connection_resource_id = replace(azurerm_cosmosdb_account.cx_backend.id, "DocumentDB", "DocumentDb")
is_manual_connection = false
subresource_names = ["MongoDB"]
}
}
resource "azurerm_private_dns_zone_virtual_network_link" "cx_backend" {
name = "privatelink.mongo.cosmos.azure.com-dblink"
resource_group_name = azurerm_resource_group.cx_backend.name
private_dns_zone_name = azurerm_private_dns_zone.cx_backend.name
virtual_network_id = azurerm_virtual_network.cx_backend.id
}
resource "azurerm_private_dns_a_record" "cx_backend_nosql" {
name = "${var.prefix}-cxbe-nosql"
zone_name = azurerm_private_dns_zone.cx_backend.name
resource_group_name = azurerm_resource_group.cx_backend.name
ttl = 10
records = ["10.0.0.4"]
tags = {
# Even when we create using Terraform, this tag gets auto-added
"creator" = "created by private endpoint wwtprod-cxbeDbEndpoint with resource guid c19c278a-2cd1-4228-9ab4-dd9d71a974b7"
}
}
resource "azurerm_private_dns_a_record" "cx_backend_nosql_loc" {
name = "${var.prefix}-cxbe-nosql-${azurerm_resource_group.cx_backend.location}"
zone_name = azurerm_private_dns_zone.cx_backend.name
resource_group_name = azurerm_resource_group.cx_backend.name
ttl = 10
records = ["10.0.0.5"]
tags = {
# Even when we create using Terraform, this tag gets auto-added
"creator" = "created by private endpoint wwtprod-cxbeDbEndpoint with resource guid c19c278a-2cd1-4228-9ab4-dd9d71a974b7"
}
}

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

@ -0,0 +1,59 @@
# The frontend of the Constellations web app.
#
# We are piggybacking on the app service plan for the backend. I think that is
# wise? I don't know but I assume that more apps in one plan is better than a
# plan per app.
#
# (Note that the app doesn't need to be in the same resource group as the app
# service, but a frontend resource group for just the one app seems a little
# pointless.)
##resource "azurerm_resource_group" "cx_frontend" {
## name = "${var.prefix}-cxfrontend"
## location = var.location
##
## lifecycle {
## prevent_destroy = true
## }
##}
resource "azurerm_linux_web_app" "cx_frontend" {
name = "${var.prefix}-cxfe"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
service_plan_id = azurerm_service_plan.cx_backend.id
app_settings = {
"NUXT_PUBLIC_API_URL" = "https://api.${var.tld}"
"NUXT_PUBLIC_GOOGLE_ANALYTICS_TAG" = var.googleAnalyticsTag
"NUXT_PUBLIC_HOST_URL" = "https://${var.tld}"
"NUXT_PUBLIC_KEYCLOAK_URL" = "https://${var.tld}/auth"
}
site_config {
always_on = true
ftps_state = "FtpsOnly"
# A funky custom start command is needed because our Zip-based deployment
# breaks symlinks in the app tree, and nodejs ends up having a problem
# running `.bin/nuxt` because it doesn't realize that it should be treated
# as an `.mjs` file.
app_command_line = "node node_modules/nuxt/bin/nuxt.mjs start"
}
}
# "stage" slot identical but not always_on. Note that most config/settings
# swap when you swap deployment slots, so this slot and production must
# be kept in sync. Fortunately the always_on setting stays put.
resource "azurerm_linux_web_app_slot" "cx_frontend_stage" {
name = "stage"
app_service_id = azurerm_linux_web_app.cx_frontend.id
app_settings = azurerm_linux_web_app.cx_frontend.app_settings
site_config {
always_on = false
ftps_state = "FtpsOnly"
app_command_line = "node node_modules/nuxt/bin/nuxt.mjs start"
}
}

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

@ -0,0 +1,57 @@
# The Keycloak ident/auth service for Constellations
#
# See the `README.md` in `wwt-constellations-backend` for some
# guidance about how to initialize the server.
resource "azurerm_linux_web_app" "keycloak" {
name = "${var.prefix}-keycloak"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
service_plan_id = azurerm_service_plan.cx_backend.id
app_settings = {
"KC_DB" = "postgres"
"KC_DB_URL" = "jdbc:postgresql://${azurerm_private_dns_a_record.cx_backend_sql.fqdn}/keycloak?sslmode=prefer&sslrootcert=/etc/ssl/certs/ca-bundle.crt"
"KC_DB_USERNAME" = "psqladmin@${azurerm_private_dns_a_record.cx_backend_sql.name}"
"KC_DB_PASSWORD" = var.cxsqlAdminPassword
"KC_HOSTNAME" = "${var.tld}"
"KC_HOSTNAME_ADMIN" = "${var.tld}"
"KC_HOSTNAME_STRICT" = "false"
"KC_HTTP_RELATIVE_PATH" = "/auth"
"KC_PROXY" = "edge"
"KEYCLOAK_ADMIN" = "wwtadmin"
"KEYCLOAK_ADMIN_PASSWORD" = var.cxkeycloakAdminPassword
}
https_only = false
site_config {
always_on = true
ftps_state = "Disabled"
vnet_route_all_enabled = true
app_command_line = "start"
application_stack {
docker_image_name = "keycloak/keycloak:22.0"
docker_registry_url = "https://quay.io"
}
}
virtual_network_subnet_id = azurerm_subnet.cx_backend_keycloak.id
}
resource "azurerm_subnet" "cx_backend_keycloak" {
name = "${var.prefix}-cxbeKcSubnet"
resource_group_name = azurerm_resource_group.cx_backend.name
virtual_network_name = azurerm_virtual_network.cx_backend.name
address_prefixes = ["10.0.6.0/24"]
delegation {
name = "dlg-appServices"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
}

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

@ -0,0 +1,94 @@
# The backing database for the Constellations Keycloak service
#
# See remarks in `constellations-backbase.tf` for some information that can
# hopefully be used to directly connect to this server, if ever needed.
resource "azurerm_postgresql_server" "cxsql" {
name = "${var.prefix}-cxsql"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
sku_name = "GP_Gen5_2"
version = "11"
storage_mb = 16384
backup_retention_days = 35
geo_redundant_backup_enabled = true
auto_grow_enabled = true
public_network_access_enabled = false
ssl_enforcement_enabled = true
ssl_minimal_tls_version_enforced = "TLS1_2"
infrastructure_encryption_enabled = false
administrator_login = "psqladmin"
administrator_login_password = var.cxsqlAdminPassword
}
resource "azurerm_postgresql_database" "keycloak" {
name = "keycloak"
resource_group_name = azurerm_resource_group.cx_backend.name
server_name = azurerm_postgresql_server.cxsql.name
charset = "UTF8"
collation = "English_United States.1252"
}
# Supporting vnet/private-endpoint stuff
resource "azurerm_subnet" "cx_backend_sql" {
name = "${var.prefix}-cxbeSqlSubnet"
resource_group_name = azurerm_resource_group.cx_backend.name
virtual_network_name = azurerm_virtual_network.cx_backend.name
address_prefixes = ["10.0.4.0/24"]
}
resource "azurerm_private_dns_zone" "cx_sql" {
name = "privatelink.postgres.database.azure.com"
resource_group_name = azurerm_resource_group.cx_backend.name
}
resource "azurerm_private_endpoint" "cx_backend_sql" {
name = "${var.prefix}-cxbeSqlEndpoint"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
subnet_id = azurerm_subnet.cx_backend_sql.id
private_dns_zone_group {
name = "default"
private_dns_zone_ids = [azurerm_private_dns_zone.cx_sql.id]
}
private_service_connection {
name = "${var.prefix}-cxbeSqlEndpoint"
private_connection_resource_id = azurerm_postgresql_server.cxsql.id
is_manual_connection = false
subresource_names = ["postgresqlServer"]
}
}
resource "azurerm_private_dns_zone_virtual_network_link" "cx_sql" {
name = "privatelink.postgres.database.azure.com-sqllink"
resource_group_name = azurerm_resource_group.cx_backend.name
private_dns_zone_name = azurerm_private_dns_zone.cx_sql.name
virtual_network_id = azurerm_virtual_network.cx_backend.id
}
resource "azurerm_private_dns_a_record" "cx_backend_sql" {
name = "${var.prefix}-cxsql"
zone_name = azurerm_private_dns_zone.cx_sql.name
resource_group_name = azurerm_resource_group.cx_backend.name
ttl = 10
records = ["10.0.4.4"]
tags = {
# Even when we create using Terraform, this tag gets auto-added
"creator" = "created by private endpoint wwtprod-cxbeSqlEndpoint with resource guid 5b0a25e2-8040-4ce1-8ace-eae834d4fbb1"
}
}
resource "azurerm_private_dns_a_record" "cx_backend_sql_loc" {
name = "${var.prefix}-cxsql-${azurerm_resource_group.cx_backend.location}"
zone_name = azurerm_private_dns_zone.cx_sql.name
resource_group_name = azurerm_resource_group.cx_backend.name
ttl = 10
records = ["10.0.4.5"]
}

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

@ -0,0 +1,166 @@
# The previewer microservice for the Constellations framework.
#
# The setup is complicated, perhaps more complicated than it needs to be. The
# fundamental issue is that we want this web app to not be publicly available;
# it should only be accessible from our VPN.
#
# - To make that be the case, we need to associate it with a "private endpoint"
# - But, it seems that in order for it to be able to communicate with the
# MongoDB, the app also needs to be assigned to a "delegated" vnet subnet.
# - Azure "app service plans" have a limit of two vnets associations per
# service, and the backend/keycloak plan has one for each of those. So this
# needs to be on its own plan. This works out OK anyway since we expect
# the previewer to benefit from autoscaling independent of other backend services.
# - Private endpoints are associated with subnets, but can't be associated with
# delegated subnets. So the app lives on two different subnets.
#
# That last piece makes me feel like I'm doing something wrong, but I haven't
# been able to figure out a better setup.
#
# Because the app only provides a private endpoint, much of the standard Azure
# tooling does not work, or does not work conveniently. As far as I can tell, to
# do much of anything we need to set up a bastion host (see
# `constellations-bastion.tf`) and do stuff in the terminal. To look at logs:
#
# ```
# curl 'https://$wwtprod-cxpv:(pwd)@wwtprod-cxpv.scm.azurewebsites.net/api/logs/docker/zip' --output docker.zip
# curl 'https://$wwtprod-cxpv:(pwd)@wwtprod-cxpv.scm.azurewebsites.net/api/logstream'
# ```
#
# Here, you can get the username/password from the deployment center settings of
# webhook URL. Manually trigger Docker update:
#
# ```
# curl -X POST 'https://$wwtprod-cxpv:(pwd)@wwtprod-cxpv.scm.azurewebsites.net/api/registry/webhook'
# ```
#
# etc.
resource "azurerm_linux_web_app" "cx_previewer" {
name = "${var.prefix}-cxpv"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
service_plan_id = azurerm_service_plan.cx_previewer.id
app_settings = {
"CONSTELLATIONS_MAX_THREADS" = "2"
"MONGO_CONNECTION_STRING" = azurerm_cosmosdb_account.cx_backend.connection_strings[0]
"AZURE_STORAGE_CONNECTION_STRING" = azurerm_storage_account.constellations.primary_connection_string
"NUXT_PUBLIC_API_URL" = "https://api.${var.tld}"
#"CX_PREVIEW_DUMPIO" = "true"
#"CX_PREVIEW_LOG_LEVEL" = "debug"
}
site_config {
always_on = false
ftps_state = "FtpsOnly"
vnet_route_all_enabled = true
app_command_line = "node server/dist/server.js"
application_stack {
docker_image_name = "aasworldwidetelescope/constellations-previewer:latest"
docker_registry_url = "https://index.docker.io"
}
}
virtual_network_subnet_id = azurerm_subnet.cx_previewer.id
logs {
detailed_error_messages = false
failed_request_tracing = false
http_logs {
file_system {
retention_in_days = 0
retention_in_mb = 35
}
}
}
}
# Separate service plan: as noted above, previewer demands are expected to scale
# up and down strongly, and we have to due to limits on the number of vnets
# associated with app service plans.
resource "azurerm_service_plan" "cx_previewer" {
name = "${var.prefix}-cxpv"
resource_group_name = azurerm_resource_group.cx_backend.name
location = azurerm_resource_group.cx_backend.location
os_type = "Linux"
sku_name = "P1v2"
}
# Supporting vnet/private-endpoint stuff
resource "azurerm_subnet" "cx_previewer" {
name = "${var.prefix}-cxpv"
resource_group_name = azurerm_resource_group.cx_backend.name
virtual_network_name = azurerm_virtual_network.cx_backend.name
address_prefixes = ["10.0.10.0/24"]
delegation {
name = "dlg-appServices"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
}
resource "azurerm_private_dns_zone" "cx_previewer" {
name = "privatelink.azurewebsites.net"
resource_group_name = azurerm_resource_group.cx_backend.name
}
resource "azurerm_private_endpoint" "cx_previewer" {
name = "${var.prefix}-cxpvEndpoint"
location = azurerm_resource_group.cx_backend.location
resource_group_name = azurerm_resource_group.cx_backend.name
subnet_id = azurerm_subnet.cx_backend_main.id
private_dns_zone_group {
name = "default"
private_dns_zone_ids = [azurerm_private_dns_zone.cx_previewer.id]
}
private_service_connection {
name = "${var.prefix}-cxpvEndpoint"
private_connection_resource_id = azurerm_linux_web_app.cx_previewer.id
is_manual_connection = false
subresource_names = ["sites"]
}
}
resource "azurerm_private_dns_zone_virtual_network_link" "cx_previewer" {
name = "privatelink.azurewebsites.net-link"
resource_group_name = azurerm_resource_group.cx_backend.name
private_dns_zone_name = azurerm_private_dns_zone.cx_previewer.name
virtual_network_id = azurerm_virtual_network.cx_backend.id
}
resource "azurerm_private_dns_a_record" "cx_previewer_server" {
name = "${var.prefix}-cxpv"
zone_name = azurerm_private_dns_zone.cx_previewer.name
resource_group_name = azurerm_resource_group.cx_backend.name
ttl = 10
records = [azurerm_private_endpoint.cx_previewer.private_service_connection[0].private_ip_address]
tags = {
# Even when we create using Terraform, this tag gets auto-added
"creator" = "created by private endpoint wwtprod-cxpvEndpoint with resource guid 3cdf40ad-aab6-4486-846d-3887bcd38075"
}
}
resource "azurerm_private_dns_a_record" "cx_previewer_server_scm" {
name = "${var.prefix}-cxpv.scm"
zone_name = azurerm_private_dns_zone.cx_previewer.name
resource_group_name = azurerm_resource_group.cx_backend.name
ttl = 10
records = [azurerm_private_endpoint.cx_previewer.private_service_connection[0].private_ip_address]
tags = {
# Even when we create using Terraform, this tag gets auto-added
"creator" = "created by private endpoint wwtprod-cxpvEndpoint with resource guid 3cdf40ad-aab6-4486-846d-3887bcd38075"
}
}

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

@ -1,6 +1,6 @@
# The DNS configuration.
#
# Not (yet) described here:
# Not (yet) described for the flagship:
# - @ SOA record
# - @ NS record
# - `mail` A record
@ -193,3 +193,44 @@ resource "azurerm_dns_cname_record" "content" {
ttl = 3600
record = "wwtelescope.vo.msecnd.net."
}
# wwtassets.org zone
resource "azurerm_dns_zone" "assets" {
name = "wwtassets.org"
resource_group_name = azurerm_resource_group.support.name
lifecycle {
prevent_destroy = true
}
}
resource "azurerm_dns_a_record" "assets_root" {
name = "@"
zone_name = azurerm_dns_zone.assets.name
resource_group_name = azurerm_dns_zone.assets.resource_group_name
ttl = 3600
records = [azurerm_linux_web_app.core_nginx.outbound_ip_address_list[length(azurerm_linux_web_app.core_nginx.outbound_ip_address_list) - 1]]
}
resource "azurerm_dns_txt_record" "assets_root" {
name = "@"
zone_name = azurerm_dns_zone.assets.name
resource_group_name = azurerm_dns_zone.assets.resource_group_name
ttl = 3600
record {
value = "${azurerm_linux_web_app.core_nginx.default_hostname}."
}
}
resource "azurerm_dns_txt_record" "assets_verify" {
name = "asuid"
zone_name = azurerm_dns_zone.assets.name
resource_group_name = azurerm_dns_zone.assets.resource_group_name
ttl = 3600
record {
value = lower(azurerm_linux_web_app.core_nginx.custom_domain_verification_id)
}
}

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

@ -94,6 +94,22 @@ resource "azurerm_storage_account" "permanent_data_mars" {
}
}
// The "constellations" storage account hosts data related to the Constellations
// framework.
resource "azurerm_storage_account" "constellations" {
name = "${var.prefix}cxdata"
resource_group_name = azurerm_resource_group.permanent_data.name
location = azurerm_resource_group.permanent_data.location
account_tier = "Standard"
account_replication_type = "GRS"
enable_https_traffic_only = false
min_tls_version = "TLS1_0" # added to reflect ground truth 2022-Sep
lifecycle {
prevent_destroy = true
}
}
// The "wwtcore" database server hosts the AstroObjects and WWTTours databases.
resource "azurerm_mssql_server" "permanent_data_wwtcore_db_server" {
name = var.legacyNameWwtcoreDBServer

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

@ -12,6 +12,41 @@ variable "oldPrefix" {
description = "A different, inferior public resource prefix label used to set up the production system"
}
variable "tld" {
description = "The top-level domain of the website"
}
// Constellations stuff
variable "cxsqlAdminPassword" {
// Sync with LastPass. Arbitrary, not connected to any external services.
description = "The administrator password for the Constellations PostgreSQL database"
sensitive = true
}
variable "cxkeycloakAdminPassword" {
// Sync with LastPass. Arbitrary, not connected to any external services.
description = "The administrator password for the Constellations Keycloak server"
sensitive = true
}
variable "googleAnalyticsTag" {
description = "The Google Analytics tag for frontend telemetry (of the form G-XXXXXXXXXX)"
}
variable "sessionSecrets" {
// Sync with LastPass. Arbitrary, not connected to any external services.
description = "Space-separated list of secrets for backend session management"
sensitive = true
}
variable "superuserAccountId" {
# I can't see how it would be a problem if this value leaked somehow, but just
# to be safe we mark it as sensitive.
description = "The account ID of an account with special admin privileges"
sensitive = true
}
// Names for "legacy" resources -- preexisting assets that we have imported into
// Terraform.
@ -59,26 +94,32 @@ variable "liveClientRedirectUrlMap" {
variable "liveClientSecret" {
description = "The OAuth app secret"
sensitive = true
}
variable "communitiesDbAdminPassword" {
description = "The password to the communities database server admin account"
sensitive = true
}
variable "wwtcoreDbAdminPassword" {
description = "The password to the wwtcore database server admin account"
sensitive = true
}
variable "layerscapeDbPassword" {
description = "The password to the Layerscape database user account"
sensitive = true
}
variable "wwttoursDbPassword" {
description = "The password to the WWTTours database user account"
sensitive = true
}
variable "appLogSasUrl" {
description = "Azure Blob Storage SAS URL for Windows web app diagnostic logs"
sensitive = true
}
variable "googleSiteVerificationTag1" {

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

@ -140,7 +140,23 @@ resource "azurerm_application_gateway" "frontend" {
fqdns = [azurerm_windows_web_app.communities.default_hostname]
}
backend_address_pool {
name = "keycloak"
fqdns = [azurerm_linux_web_app.keycloak.default_hostname]
}
backend_address_pool {
name = "cx-frontend"
fqdns = [azurerm_linux_web_app.cx_frontend.default_hostname]
}
# Backend HTTP settings
#
# Modifying this collection is tricky. If Terraform sees any disagreement
# between its expectations and reality, it seems to want to recreate all of
# the settings, which feels risky. Make changes through the Portal UI and then
# then do a `terraform apply -refresh-only` to sync up Terraform's state with
# the ground truth. That seems to work.
backend_http_settings {
name = "webstatic-http-setting"
@ -175,6 +191,31 @@ resource "azurerm_application_gateway" "frontend" {
trusted_root_certificate_names = []
}
backend_http_settings {
name = "keycloak"
cookie_based_affinity = "Disabled"
pick_host_name_from_backend_address = true
port = 80
protocol = "Http"
request_timeout = 20
trusted_root_certificate_names = []
probe_name = "keycloak"
}
# Probes
probe {
# Keycloak needs a custom probe since it only handles requests within the
# /auth/ prefix.
name = "keycloak"
pick_host_name_from_backend_http_settings = true
interval = 30
timeout = 30
protocol = "Http"
path = "/auth/"
unhealthy_threshold = 3
}
# Request routing rules
request_routing_rule {
@ -193,9 +234,13 @@ resource "azurerm_application_gateway" "frontend" {
priority = 10010
}
# First of two path maps that should be kept identical except for HTTP vs. HTTPS
#
# When adding rules with Terraform, you have to append them, otherwise Terraform
# wants to delete-and-recreate them, which seems risky.
url_path_map {
name = "anyhost-https-path-routing"
default_backend_address_pool_name = "wwtappgw1-nginx-core-prod-backend"
default_backend_address_pool_name = "cx-frontend"
default_backend_http_settings_name = "rehost-http-setting"
default_rewrite_rule_set_name = "global-cors-and-cache"
@ -278,11 +323,33 @@ resource "azurerm_application_gateway" "frontend" {
]
rewrite_rule_set_name = "global-cors-and-cache"
}
path_rule {
name = "keycloak"
backend_address_pool_name = "keycloak"
backend_http_settings_name = "keycloak"
paths = [
"/auth/*",
]
}
path_rule {
name = "cx-frontend"
backend_address_pool_name = "cx-frontend"
backend_http_settings_name = "rehost-http-setting"
paths = [
"/@*",
"/_cxadmin/*",
"/_nuxt/*",
"/silent-check-sso",
]
}
}
# Second of two path maps that should be kept identical except for HTTP vs. HTTPS
url_path_map {
name = "anyhost-http-path-routing"
default_backend_address_pool_name = "wwtappgw1-nginx-core-prod-backend"
default_backend_address_pool_name = "cx-frontend"
default_backend_http_settings_name = "rehost-http-setting"
default_rewrite_rule_set_name = "global-cors-and-cache"
@ -365,6 +432,27 @@ resource "azurerm_application_gateway" "frontend" {
]
rewrite_rule_set_name = "global-cors-and-cache"
}
path_rule {
name = "keycloak"
backend_address_pool_name = "keycloak"
backend_http_settings_name = "keycloak"
paths = [
"/auth/*",
]
}
path_rule {
name = "cx-frontend"
backend_address_pool_name = "cx-frontend"
backend_http_settings_name = "rehost-http-setting"
paths = [
"/@*",
"/_cxadmin/*",
"/_nuxt/*",
"/silent-check-sso",
]
}
}
rewrite_rule_set {