From 426b15e92463be21ac8a8f94ce8e4537c7f8f2e0 Mon Sep 17 00:00:00 2001 From: Charles Nykamp <16085675+cqnykamp@users.noreply.github.com> Date: Sat, 18 Jul 2026 11:20:57 -0500 Subject: [PATCH 1/3] feat(infra): unify redirect stack; move beta redirect into doenet.org zone Generalize redirect.yml into one template that serves both the beta.doenet.org zone redirect and the www.doenet.org single-host redirect, and add the www stack (prod-doenet-www-redirect) that 301s www.doenet.org -> doenet.org so the site keeps a single canonical host. Template changes (redirect.yml): - Add RedirectSubdomains ("true"/"false", default true). true keeps the existing apex + *. behavior with subdomain-label carry; false emits just the single host (no wildcard SAN, no wildcard records) so it is safe to point at a zone that already serves other subdomains. - Rename SourceHostedZoneName -> SourceHost and add RedirectName, a dot-free label used for the per-account-unique CloudFront function name and tags. - The one function body (query-string rebuild + host swap) now lives in a single place instead of being duplicated. Move the beta redirect off its own delegated hosted zone into the doenet.org parent zone (SourceHostedZoneId -> Z08210421B0UEN4PYID0X); beta.doenet.org is a subdomain of doenet.org and only needs a separate zone if delegated, which a dumb redirect no longer warrants. Both redirects now write into the doenet.org zone. Deleting the now-redundant beta.doenet.org hosted zone + its NS delegation is a follow-up (see PR notes) since it is an order-sensitive live DNS change. Deleted redirect-host.yml (folded into redirect.yml). Co-Authored-By: Claude Opus 4.8 --- .../prod-doenet-redirect.params | 12 ++- .../prod-doenet-www-redirect.params | 26 ++++++ infra/cloudformation/redirect.yml | 87 +++++++++++++------ infra/prod.aws | 3 + 4 files changed, 99 insertions(+), 29 deletions(-) create mode 100644 infra/cloudformation/prod-doenet-www-redirect.params diff --git a/infra/cloudformation/prod-doenet-redirect.params b/infra/cloudformation/prod-doenet-redirect.params index 794546b38..b9e3344e3 100644 --- a/infra/cloudformation/prod-doenet-redirect.params +++ b/infra/cloudformation/prod-doenet-redirect.params @@ -4,15 +4,23 @@ "ParameterValue": "prod" }, { - "ParameterKey": "SourceHostedZoneName", + "ParameterKey": "RedirectName", + "ParameterValue": "beta" + }, + { + "ParameterKey": "SourceHost", "ParameterValue": "beta.doenet.org" }, { "ParameterKey": "SourceHostedZoneId", - "ParameterValue": "Z0126382ZHTT46RB59N8" + "ParameterValue": "Z08210421B0UEN4PYID0X" }, { "ParameterKey": "TargetDomainName", "ParameterValue": "doenet.org" + }, + { + "ParameterKey": "RedirectSubdomains", + "ParameterValue": "true" } ] diff --git a/infra/cloudformation/prod-doenet-www-redirect.params b/infra/cloudformation/prod-doenet-www-redirect.params new file mode 100644 index 000000000..ff981298d --- /dev/null +++ b/infra/cloudformation/prod-doenet-www-redirect.params @@ -0,0 +1,26 @@ +[ + { + "ParameterKey": "EnvironmentName", + "ParameterValue": "prod" + }, + { + "ParameterKey": "RedirectName", + "ParameterValue": "www" + }, + { + "ParameterKey": "SourceHost", + "ParameterValue": "www.doenet.org" + }, + { + "ParameterKey": "SourceHostedZoneId", + "ParameterValue": "Z08210421B0UEN4PYID0X" + }, + { + "ParameterKey": "TargetDomainName", + "ParameterValue": "doenet.org" + }, + { + "ParameterKey": "RedirectSubdomains", + "ParameterValue": "false" + } +] diff --git a/infra/cloudformation/redirect.yml b/infra/cloudformation/redirect.yml index ce3f09dc8..65ec8c40d 100644 --- a/infra/cloudformation/redirect.yml +++ b/infra/cloudformation/redirect.yml @@ -1,11 +1,14 @@ AWSTemplateFormatVersion: "2010-09-09" Description: >- - Redirect distribution: permanently (301) redirects a legacy host (e.g. - beta.doenet.org and *.beta.doenet.org) to a new domain (e.g. doenet.org), - preserving the subdomain label, path, and query string. Must be deployed to - us-east-1 so the ACM certificate can attach to CloudFront (and, because SSM - parameters are regional, this stack takes its hosted-zone values as literals - rather than /prod/PublicHostedZone* SSM refs, which live in us-east-2). + Redirect distribution: permanently (301) redirects a source host to a target + domain, preserving path and query string. With RedirectSubdomains=true it also + covers every subdomain (*.) and carries the subdomain label over to the + target (media. -> media.); the source must then be a hosted-zone + apex so the wildcard cert and records are valid. With RedirectSubdomains=false it + redirects just the single host (e.g. www.). Must be deployed to us-east-1 + so the ACM certificate can attach to CloudFront (and, because SSM parameters are + regional, this stack takes its hosted-zone values as literals rather than + /prod/PublicHostedZone* SSM refs, which live in us-east-2). Parameters: EnvironmentName: @@ -13,35 +16,60 @@ Parameters: Description: The environment name (the SSM prefix path name) MaxLength: 10 - SourceHostedZoneName: + RedirectName: Type: String - Description: Legacy host to redirect FROM (e.g. beta.doenet.org) + Description: >- + Short dot-free label identifying this redirect (e.g. "beta", "www"). Used + for the CloudFront function name and resource tags, which must be unique per + account, so each redirect stack needs a distinct value. + AllowedPattern: "[a-zA-Z0-9-_]+" + + SourceHost: + Type: String + Description: >- + Host to redirect FROM (e.g. beta.doenet.org or www.doenet.org). With + RedirectSubdomains=true this must be a hosted-zone apex. SourceHostedZoneId: Type: String - Description: Route 53 hosted zone ID that owns SourceHostedZoneName + Description: >- + Route 53 hosted zone ID that holds SourceHost's records. This can be the + zone for SourceHost itself, or a parent zone (e.g. hold beta.doenet.org + records directly in the doenet.org zone rather than a delegated subzone). TargetDomainName: Type: String Description: Domain to redirect TO (e.g. doenet.org) + RedirectSubdomains: + Type: String + Description: >- + "true" to also redirect *., carrying the subdomain label to the + target; "false" to redirect only the single SourceHost. + AllowedValues: ["true", "false"] + Default: "true" + +Conditions: + IncludeSubdomains: !Equals [!Ref RedirectSubdomains, "true"] + Resources: - # Covers the legacy host (e.g. beta.doenet.org) and every subdomain - # (*.beta.doenet.org) such as media.beta.doenet.org. The wildcard does not - # match the apex, so both names are listed explicitly. Certificate: Type: AWS::CertificateManager::Certificate Properties: - DomainName: !Ref SourceHostedZoneName - SubjectAlternativeNames: - - !Sub "*.${SourceHostedZoneName}" + DomainName: !Ref SourceHost + # A wildcard SAN is only valid when the source is a zone apex, so it is + # added only in subdomain mode. + SubjectAlternativeNames: !If + - IncludeSubdomains + - [!Sub "*.${SourceHost}"] + - !Ref "AWS::NoValue" ValidationMethod: DNS # ACM uses one shared validation CNAME for a domain and its wildcard, so # only the apex is listed here; listing the wildcard too would make # CloudFormation write the identical Route 53 record twice in one change # batch and fail with InvalidChangeBatch. The single record validates both. DomainValidationOptions: - - DomainName: !Ref SourceHostedZoneName + - DomainName: !Ref SourceHost HostedZoneId: !Ref SourceHostedZoneId # Runs on viewer-request and returns the 301 before the request is ever @@ -49,18 +77,21 @@ Resources: # source apex for the target while keeping any subdomain label, e.g. # beta.doenet.org/foo?x=1 -> https://doenet.org/foo?x=1 # media.beta.doenet.org/img.png -> https://media.doenet.org/img.png + # In single-host mode no subdomain hosts are routed here, so the carry branch + # never fires and it reduces to a plain host swap to the apex target. RedirectFunction: Type: AWS::CloudFront::Function Properties: - Name: !Sub "${EnvironmentName}-host-redirect" + # Names are unique per account, so they are keyed on the redirect label. + Name: !Sub "${EnvironmentName}-${RedirectName}-redirect" AutoPublish: true FunctionConfig: - Comment: 301-redirect a legacy host to the target domain, preserving subdomain/path/query + Comment: 301-redirect a source host to the target domain, preserving subdomain/path/query Runtime: cloudfront-js-2.0 FunctionCode: !Sub | function handler(event) { var request = event.request; - var source = '${SourceHostedZoneName}'; + var source = '${SourceHost}'; var target = '${TargetDomainName}'; var host = request.headers.host ? request.headers.host.value : source; @@ -115,10 +146,10 @@ Resources: Enabled: true HttpVersion: http2and3 IPV6Enabled: true - Comment: !Sub "Redirect ${SourceHostedZoneName} -> ${TargetDomainName}" + Comment: !Sub "Redirect ${SourceHost} -> ${TargetDomainName}" Aliases: - - !Ref SourceHostedZoneName - - !Sub "*.${SourceHostedZoneName}" + - !Ref SourceHost + - !If [IncludeSubdomains, !Sub "*.${SourceHost}", !Ref "AWS::NoValue"] Origins: # Placeholder only: the viewer-request function returns a 301 before any # request reaches an origin, so this is never contacted. Pointing it at @@ -148,14 +179,14 @@ Resources: MinimumProtocolVersion: TLSv1.2_2021 Tags: - Key: Name - Value: !Sub "${EnvironmentName}-host-redirect-cloudfront" + Value: !Sub "${EnvironmentName}-${RedirectName}-redirect-cloudfront" # Z2FDTNDATAQYW2 is CloudFront's fixed hosted zone ID for alias targets. ApexRecordV4: Type: AWS::Route53::RecordSet Properties: HostedZoneId: !Ref SourceHostedZoneId - Name: !Sub "${SourceHostedZoneName}." + Name: !Sub "${SourceHost}." Type: A AliasTarget: DNSName: !GetAtt RedirectDistribution.DomainName @@ -166,7 +197,7 @@ Resources: Type: AWS::Route53::RecordSet Properties: HostedZoneId: !Ref SourceHostedZoneId - Name: !Sub "${SourceHostedZoneName}." + Name: !Sub "${SourceHost}." Type: AAAA AliasTarget: DNSName: !GetAtt RedirectDistribution.DomainName @@ -175,9 +206,10 @@ Resources: WildcardRecordV4: Type: AWS::Route53::RecordSet + Condition: IncludeSubdomains Properties: HostedZoneId: !Ref SourceHostedZoneId - Name: !Sub "*.${SourceHostedZoneName}." + Name: !Sub "*.${SourceHost}." Type: A AliasTarget: DNSName: !GetAtt RedirectDistribution.DomainName @@ -186,9 +218,10 @@ Resources: WildcardRecordV6: Type: AWS::Route53::RecordSet + Condition: IncludeSubdomains Properties: HostedZoneId: !Ref SourceHostedZoneId - Name: !Sub "*.${SourceHostedZoneName}." + Name: !Sub "*.${SourceHost}." Type: AAAA AliasTarget: DNSName: !GetAtt RedirectDistribution.DomainName diff --git a/infra/prod.aws b/infra/prod.aws index 786bb3df1..9dc051fe5 100644 --- a/infra/prod.aws +++ b/infra/prod.aws @@ -19,6 +19,7 @@ STACKS['prod-doenet-frontend']='s3-hosted-stackset' STACKS['prod-doenet-media-cert']='cdn-cert' STACKS['prod-doenet-media-cdn']='cdn' STACKS['prod-doenet-redirect']='redirect' +STACKS['prod-doenet-www-redirect']='redirect' STACKS['prod-discourse-forums']='discourse-forums-ec2' @@ -27,6 +28,7 @@ STACK_REGION['prod-doenet-media-cert']='us-east-1' # CloudFront requires its ACM certificate in us-east-1; this stack bundles the # cert with the distribution, so the whole stack is deployed there. STACK_REGION['prod-doenet-redirect']='us-east-1' +STACK_REGION['prod-doenet-www-redirect']='us-east-1' declare -g -a STACK_ORDER STACK_ORDER=( @@ -41,6 +43,7 @@ STACK_ORDER=( prod-doenet-media-cdn prod-doenet-frontend prod-doenet-redirect + prod-doenet-www-redirect ) declare -g -a SAM_STACKS From 9f29c9a9d7e9831ba28cf0b12c5acfca3a03f1f1 Mon Sep 17 00:00:00 2001 From: Charles Nykamp <16085675+cqnykamp@users.noreply.github.com> Date: Sat, 18 Jul 2026 11:20:57 -0500 Subject: [PATCH 2/3] feat(infra): unify redirect stack; move beta redirect into doenet.org zone Generalize redirect.yml into one template that serves both the beta.doenet.org zone redirect and the www.doenet.org single-host redirect, and add the www stack (prod-doenet-www-redirect) that 301s www.doenet.org -> doenet.org so the site keeps a single canonical host. Template changes (redirect.yml): - Add RedirectSubdomains ("true"/"false", default true). true keeps the existing apex + *. behavior with subdomain-label carry; false emits just the single host (no wildcard SAN, no wildcard records) so it is safe to point at a zone that already serves other subdomains. - Replace the source/target params with a single RedirectName label (e.g. "beta", "www") plus the repo-standard PublicHostedZoneName / PublicHostedZoneId (matching the sibling us-east-1 media-cert stack, which also passes them as literals). The source host is derived as . and the redirect target is the apex PublicHostedZoneName, so neither needs its own param; RedirectName also forms the per-account-unique CloudFront function name and tags. - The one function body (query-string rebuild + host swap) now lives in a single place instead of being duplicated. Move the beta redirect off its own delegated hosted zone into the doenet.org parent zone (SourceHostedZoneId -> Z08210421B0UEN4PYID0X); beta.doenet.org is a subdomain of doenet.org and only needs a separate zone if delegated, which a dumb redirect no longer warrants. Both redirects now write into the doenet.org zone. Deleting the now-redundant beta.doenet.org hosted zone + its NS delegation is a follow-up (see PR notes) since it is an order-sensitive live DNS change. Deleted redirect-host.yml (folded into redirect.yml). Co-Authored-By: Claude Opus 4.8 --- .../prod-doenet-redirect.params | 16 ++- .../prod-doenet-www-redirect.params | 22 ++++ infra/cloudformation/redirect.yml | 111 ++++++++++++------ infra/prod.aws | 3 + 4 files changed, 109 insertions(+), 43 deletions(-) create mode 100644 infra/cloudformation/prod-doenet-www-redirect.params diff --git a/infra/cloudformation/prod-doenet-redirect.params b/infra/cloudformation/prod-doenet-redirect.params index 794546b38..f963465d1 100644 --- a/infra/cloudformation/prod-doenet-redirect.params +++ b/infra/cloudformation/prod-doenet-redirect.params @@ -4,15 +4,19 @@ "ParameterValue": "prod" }, { - "ParameterKey": "SourceHostedZoneName", - "ParameterValue": "beta.doenet.org" + "ParameterKey": "RedirectName", + "ParameterValue": "beta" }, { - "ParameterKey": "SourceHostedZoneId", - "ParameterValue": "Z0126382ZHTT46RB59N8" + "ParameterKey": "PublicHostedZoneName", + "ParameterValue": "doenet.org" }, { - "ParameterKey": "TargetDomainName", - "ParameterValue": "doenet.org" + "ParameterKey": "PublicHostedZoneId", + "ParameterValue": "Z08210421B0UEN4PYID0X" + }, + { + "ParameterKey": "RedirectSubdomains", + "ParameterValue": "true" } ] diff --git a/infra/cloudformation/prod-doenet-www-redirect.params b/infra/cloudformation/prod-doenet-www-redirect.params new file mode 100644 index 000000000..f6e6e7aac --- /dev/null +++ b/infra/cloudformation/prod-doenet-www-redirect.params @@ -0,0 +1,22 @@ +[ + { + "ParameterKey": "EnvironmentName", + "ParameterValue": "prod" + }, + { + "ParameterKey": "RedirectName", + "ParameterValue": "www" + }, + { + "ParameterKey": "PublicHostedZoneName", + "ParameterValue": "doenet.org" + }, + { + "ParameterKey": "PublicHostedZoneId", + "ParameterValue": "Z08210421B0UEN4PYID0X" + }, + { + "ParameterKey": "RedirectSubdomains", + "ParameterValue": "false" + } +] diff --git a/infra/cloudformation/redirect.yml b/infra/cloudformation/redirect.yml index ce3f09dc8..4fcd0b04a 100644 --- a/infra/cloudformation/redirect.yml +++ b/infra/cloudformation/redirect.yml @@ -1,11 +1,16 @@ AWSTemplateFormatVersion: "2010-09-09" Description: >- - Redirect distribution: permanently (301) redirects a legacy host (e.g. - beta.doenet.org and *.beta.doenet.org) to a new domain (e.g. doenet.org), - preserving the subdomain label, path, and query string. Must be deployed to - us-east-1 so the ACM certificate can attach to CloudFront (and, because SSM - parameters are regional, this stack takes its hosted-zone values as literals - rather than /prod/PublicHostedZone* SSM refs, which live in us-east-2). + Redirect distribution: permanently (301) redirects a source host to the public + hosted zone apex, preserving path and query string. The source host is the + subdomain . (e.g. www.doenet.org, + beta.doenet.org) and the redirect target is the apex PublicHostedZoneName + (doenet.org). With RedirectSubdomains=true it also covers every subdomain + (*.) and carries the subdomain label over to the target + (media. -> media.). With RedirectSubdomains=false it redirects + just the single host. Must be deployed to us-east-1 so the ACM certificate can + attach to CloudFront; because SSM parameters are regional, this stack takes its + PublicHostedZone* values as literals rather than the /prod/PublicHostedZone* + SSM refs used elsewhere, which live in us-east-2. Parameters: EnvironmentName: @@ -13,55 +18,81 @@ Parameters: Description: The environment name (the SSM prefix path name) MaxLength: 10 - SourceHostedZoneName: + RedirectName: Type: String - Description: Legacy host to redirect FROM (e.g. beta.doenet.org) + Description: >- + The single DNS label to redirect FROM (e.g. "beta", "www"). The source host + is .. Also used for the CloudFront + function name and resource tags, which must be unique per account, so each + redirect stack needs a distinct value. + AllowedPattern: "[a-z0-9]([a-z0-9-]*[a-z0-9])?" + + PublicHostedZoneName: + Type: String + Description: >- + The public hosted zone name (e.g. doenet.org). This is both the redirect + target (the apex the source host is sent to) and the parent domain of the + source host. Passed as a literal because this stack runs in us-east-1. - SourceHostedZoneId: + PublicHostedZoneId: Type: String - Description: Route 53 hosted zone ID that owns SourceHostedZoneName + Description: >- + Route 53 hosted zone ID for PublicHostedZoneName. The redirect's records + (source apex/wildcard + cert validation) are written here, so a subdomain + redirect lives directly in the parent zone rather than a delegated subzone. - TargetDomainName: + RedirectSubdomains: Type: String - Description: Domain to redirect TO (e.g. doenet.org) + Description: >- + "true" to also redirect *., carrying the subdomain label to the + target; "false" to redirect only the single source host. + AllowedValues: ["true", "false"] + Default: "true" + +Conditions: + IncludeSubdomains: !Equals [!Ref RedirectSubdomains, "true"] Resources: - # Covers the legacy host (e.g. beta.doenet.org) and every subdomain - # (*.beta.doenet.org) such as media.beta.doenet.org. The wildcard does not - # match the apex, so both names are listed explicitly. Certificate: Type: AWS::CertificateManager::Certificate Properties: - DomainName: !Ref SourceHostedZoneName - SubjectAlternativeNames: - - !Sub "*.${SourceHostedZoneName}" + DomainName: !Sub "${RedirectName}.${PublicHostedZoneName}" + # A wildcard SAN is only valid when the source is a zone apex, so it is + # added only in subdomain mode. + SubjectAlternativeNames: !If + - IncludeSubdomains + - [!Sub "*.${RedirectName}.${PublicHostedZoneName}"] + - !Ref "AWS::NoValue" ValidationMethod: DNS # ACM uses one shared validation CNAME for a domain and its wildcard, so # only the apex is listed here; listing the wildcard too would make # CloudFormation write the identical Route 53 record twice in one change # batch and fail with InvalidChangeBatch. The single record validates both. DomainValidationOptions: - - DomainName: !Ref SourceHostedZoneName - HostedZoneId: !Ref SourceHostedZoneId + - DomainName: !Sub "${RedirectName}.${PublicHostedZoneName}" + HostedZoneId: !Ref PublicHostedZoneId # Runs on viewer-request and returns the 301 before the request is ever # proxied, so the distribution's origin is never contacted. Host-swaps the # source apex for the target while keeping any subdomain label, e.g. # beta.doenet.org/foo?x=1 -> https://doenet.org/foo?x=1 # media.beta.doenet.org/img.png -> https://media.doenet.org/img.png + # In single-host mode no subdomain hosts are routed here, so the carry branch + # never fires and it reduces to a plain host swap to the apex target. RedirectFunction: Type: AWS::CloudFront::Function Properties: - Name: !Sub "${EnvironmentName}-host-redirect" + # Names are unique per account, so they are keyed on the redirect label. + Name: !Sub "${EnvironmentName}-${RedirectName}-redirect" AutoPublish: true FunctionConfig: - Comment: 301-redirect a legacy host to the target domain, preserving subdomain/path/query + Comment: 301-redirect a source host to the target domain, preserving subdomain/path/query Runtime: cloudfront-js-2.0 FunctionCode: !Sub | function handler(event) { var request = event.request; - var source = '${SourceHostedZoneName}'; - var target = '${TargetDomainName}'; + var source = '${RedirectName}.${PublicHostedZoneName}'; + var target = '${PublicHostedZoneName}'; var host = request.headers.host ? request.headers.host.value : source; // Default to the apex; if the host is a subdomain of the source, @@ -115,16 +146,20 @@ Resources: Enabled: true HttpVersion: http2and3 IPV6Enabled: true - Comment: !Sub "Redirect ${SourceHostedZoneName} -> ${TargetDomainName}" + Comment: !Sub "Redirect ${RedirectName}.${PublicHostedZoneName} -> ${PublicHostedZoneName}" Aliases: - - !Ref SourceHostedZoneName - - !Sub "*.${SourceHostedZoneName}" + - !Sub "${RedirectName}.${PublicHostedZoneName}" + - !If [ + IncludeSubdomains, + !Sub "*.${RedirectName}.${PublicHostedZoneName}", + !Ref "AWS::NoValue", + ] Origins: # Placeholder only: the viewer-request function returns a 301 before any # request reaches an origin, so this is never contacted. Pointing it at # the target keeps it valid and self-documenting. - Id: PlaceholderOrigin - DomainName: !Ref TargetDomainName + DomainName: !Ref PublicHostedZoneName CustomOriginConfig: OriginProtocolPolicy: https-only HTTPSPort: 443 @@ -148,14 +183,14 @@ Resources: MinimumProtocolVersion: TLSv1.2_2021 Tags: - Key: Name - Value: !Sub "${EnvironmentName}-host-redirect-cloudfront" + Value: !Sub "${EnvironmentName}-${RedirectName}-redirect-cloudfront" # Z2FDTNDATAQYW2 is CloudFront's fixed hosted zone ID for alias targets. ApexRecordV4: Type: AWS::Route53::RecordSet Properties: - HostedZoneId: !Ref SourceHostedZoneId - Name: !Sub "${SourceHostedZoneName}." + HostedZoneId: !Ref PublicHostedZoneId + Name: !Sub "${RedirectName}.${PublicHostedZoneName}." Type: A AliasTarget: DNSName: !GetAtt RedirectDistribution.DomainName @@ -165,8 +200,8 @@ Resources: ApexRecordV6: Type: AWS::Route53::RecordSet Properties: - HostedZoneId: !Ref SourceHostedZoneId - Name: !Sub "${SourceHostedZoneName}." + HostedZoneId: !Ref PublicHostedZoneId + Name: !Sub "${RedirectName}.${PublicHostedZoneName}." Type: AAAA AliasTarget: DNSName: !GetAtt RedirectDistribution.DomainName @@ -175,9 +210,10 @@ Resources: WildcardRecordV4: Type: AWS::Route53::RecordSet + Condition: IncludeSubdomains Properties: - HostedZoneId: !Ref SourceHostedZoneId - Name: !Sub "*.${SourceHostedZoneName}." + HostedZoneId: !Ref PublicHostedZoneId + Name: !Sub "*.${RedirectName}.${PublicHostedZoneName}." Type: A AliasTarget: DNSName: !GetAtt RedirectDistribution.DomainName @@ -186,9 +222,10 @@ Resources: WildcardRecordV6: Type: AWS::Route53::RecordSet + Condition: IncludeSubdomains Properties: - HostedZoneId: !Ref SourceHostedZoneId - Name: !Sub "*.${SourceHostedZoneName}." + HostedZoneId: !Ref PublicHostedZoneId + Name: !Sub "*.${RedirectName}.${PublicHostedZoneName}." Type: AAAA AliasTarget: DNSName: !GetAtt RedirectDistribution.DomainName diff --git a/infra/prod.aws b/infra/prod.aws index 786bb3df1..9dc051fe5 100644 --- a/infra/prod.aws +++ b/infra/prod.aws @@ -19,6 +19,7 @@ STACKS['prod-doenet-frontend']='s3-hosted-stackset' STACKS['prod-doenet-media-cert']='cdn-cert' STACKS['prod-doenet-media-cdn']='cdn' STACKS['prod-doenet-redirect']='redirect' +STACKS['prod-doenet-www-redirect']='redirect' STACKS['prod-discourse-forums']='discourse-forums-ec2' @@ -27,6 +28,7 @@ STACK_REGION['prod-doenet-media-cert']='us-east-1' # CloudFront requires its ACM certificate in us-east-1; this stack bundles the # cert with the distribution, so the whole stack is deployed there. STACK_REGION['prod-doenet-redirect']='us-east-1' +STACK_REGION['prod-doenet-www-redirect']='us-east-1' declare -g -a STACK_ORDER STACK_ORDER=( @@ -41,6 +43,7 @@ STACK_ORDER=( prod-doenet-media-cdn prod-doenet-frontend prod-doenet-redirect + prod-doenet-www-redirect ) declare -g -a SAM_STACKS From 912ba67741ccd1dc67607e48479ff64f64c279a9 Mon Sep 17 00:00:00 2001 From: Charles Nykamp <16085675+cqnykamp@users.noreply.github.com> Date: Sun, 19 Jul 2026 00:03:02 -0500 Subject: [PATCH 3/3] feat(infra): finalize redirect params, add dev3 redirect stacks Rename the redirect param files to the -doenet-redirect- stack naming convention and register the beta/www redirect stacks on dev3 for testing (dev3.doenet.org zone Z0066937J8QHRAL9H84P). Co-Authored-By: Claude Opus 4.8 --- .../dev3-doenet-redirect-beta.params | 22 +++++++++++++++++++ .../dev3-doenet-redirect-www.params | 22 +++++++++++++++++++ ...arams => prod-doenet-redirect-beta.params} | 8 +++---- ...params => prod-doenet-redirect-www.params} | 8 +++---- infra/dev3.aws | 6 +++++ infra/prod.aws | 14 ++++++------ 6 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 infra/cloudformation/dev3-doenet-redirect-beta.params create mode 100644 infra/cloudformation/dev3-doenet-redirect-www.params rename infra/cloudformation/{prod-doenet-redirect.params => prod-doenet-redirect-beta.params} (100%) rename infra/cloudformation/{prod-doenet-www-redirect.params => prod-doenet-redirect-www.params} (100%) diff --git a/infra/cloudformation/dev3-doenet-redirect-beta.params b/infra/cloudformation/dev3-doenet-redirect-beta.params new file mode 100644 index 000000000..91e65159c --- /dev/null +++ b/infra/cloudformation/dev3-doenet-redirect-beta.params @@ -0,0 +1,22 @@ +[ + { + "ParameterKey": "EnvironmentName", + "ParameterValue": "dev3" + }, + { + "ParameterKey": "RedirectName", + "ParameterValue": "beta" + }, + { + "ParameterKey": "RedirectSubdomains", + "ParameterValue": "true" + }, + { + "ParameterKey": "PublicHostedZoneName", + "ParameterValue": "dev3.doenet.org" + }, + { + "ParameterKey": "PublicHostedZoneId", + "ParameterValue": "Z0066937J8QHRAL9H84P" + } +] diff --git a/infra/cloudformation/dev3-doenet-redirect-www.params b/infra/cloudformation/dev3-doenet-redirect-www.params new file mode 100644 index 000000000..722c60591 --- /dev/null +++ b/infra/cloudformation/dev3-doenet-redirect-www.params @@ -0,0 +1,22 @@ +[ + { + "ParameterKey": "EnvironmentName", + "ParameterValue": "dev3" + }, + { + "ParameterKey": "RedirectName", + "ParameterValue": "www" + }, + { + "ParameterKey": "RedirectSubdomains", + "ParameterValue": "false" + }, + { + "ParameterKey": "PublicHostedZoneName", + "ParameterValue": "dev3.doenet.org" + }, + { + "ParameterKey": "PublicHostedZoneId", + "ParameterValue": "Z0066937J8QHRAL9H84P" + } +] diff --git a/infra/cloudformation/prod-doenet-redirect.params b/infra/cloudformation/prod-doenet-redirect-beta.params similarity index 100% rename from infra/cloudformation/prod-doenet-redirect.params rename to infra/cloudformation/prod-doenet-redirect-beta.params index f963465d1..1519bce7f 100644 --- a/infra/cloudformation/prod-doenet-redirect.params +++ b/infra/cloudformation/prod-doenet-redirect-beta.params @@ -7,6 +7,10 @@ "ParameterKey": "RedirectName", "ParameterValue": "beta" }, + { + "ParameterKey": "RedirectSubdomains", + "ParameterValue": "true" + }, { "ParameterKey": "PublicHostedZoneName", "ParameterValue": "doenet.org" @@ -14,9 +18,5 @@ { "ParameterKey": "PublicHostedZoneId", "ParameterValue": "Z08210421B0UEN4PYID0X" - }, - { - "ParameterKey": "RedirectSubdomains", - "ParameterValue": "true" } ] diff --git a/infra/cloudformation/prod-doenet-www-redirect.params b/infra/cloudformation/prod-doenet-redirect-www.params similarity index 100% rename from infra/cloudformation/prod-doenet-www-redirect.params rename to infra/cloudformation/prod-doenet-redirect-www.params index f6e6e7aac..c92180645 100644 --- a/infra/cloudformation/prod-doenet-www-redirect.params +++ b/infra/cloudformation/prod-doenet-redirect-www.params @@ -7,6 +7,10 @@ "ParameterKey": "RedirectName", "ParameterValue": "www" }, + { + "ParameterKey": "RedirectSubdomains", + "ParameterValue": "false" + }, { "ParameterKey": "PublicHostedZoneName", "ParameterValue": "doenet.org" @@ -14,9 +18,5 @@ { "ParameterKey": "PublicHostedZoneId", "ParameterValue": "Z08210421B0UEN4PYID0X" - }, - { - "ParameterKey": "RedirectSubdomains", - "ParameterValue": "false" } ] diff --git a/infra/dev3.aws b/infra/dev3.aws index e76372337..c10aecd74 100644 --- a/infra/dev3.aws +++ b/infra/dev3.aws @@ -16,11 +16,15 @@ STACKS['dev3-doenet']='service' STACKS['dev3-doenet-frontend']='s3-hosted-stackset' STACKS['dev3-doenet-media-cert']='cdn-cert' STACKS['dev3-doenet-media-cdn']='cdn' +STACKS['dev3-doenet-redirect-www']='redirect' +STACKS['dev3-doenet-redirect-beta']='redirect' STACKS['dev3-discourse-forums']='discourse-forums-ec2' declare -g -A STACK_REGION STACK_REGION['dev3-doenet-media-cert']='us-east-1' +STACK_REGION['dev3-doenet-redirect-www']='us-east-1' +STACK_REGION['dev3-doenet-redirect-beta']='us-east-1' declare -g -a STACK_ORDER STACK_ORDER=( @@ -33,6 +37,8 @@ STACK_ORDER=( dev3-doenet-media-cert dev3-doenet-media-cdn dev3-doenet-frontend + dev3-doenet-redirect-www + dev3-doenet-redirect-beta ) declare -g -a SAM_STACKS diff --git a/infra/prod.aws b/infra/prod.aws index 9dc051fe5..f1ee0e347 100644 --- a/infra/prod.aws +++ b/infra/prod.aws @@ -18,17 +18,17 @@ STACKS['prod-doenet']='service' STACKS['prod-doenet-frontend']='s3-hosted-stackset' STACKS['prod-doenet-media-cert']='cdn-cert' STACKS['prod-doenet-media-cdn']='cdn' -STACKS['prod-doenet-redirect']='redirect' -STACKS['prod-doenet-www-redirect']='redirect' +STACKS['prod-doenet-redirect-www']='redirect' +STACKS['prod-doenet-redirect-beta']='redirect' STACKS['prod-discourse-forums']='discourse-forums-ec2' declare -g -A STACK_REGION -STACK_REGION['prod-doenet-media-cert']='us-east-1' # CloudFront requires its ACM certificate in us-east-1; this stack bundles the # cert with the distribution, so the whole stack is deployed there. -STACK_REGION['prod-doenet-redirect']='us-east-1' -STACK_REGION['prod-doenet-www-redirect']='us-east-1' +STACK_REGION['prod-doenet-media-cert']='us-east-1' +STACK_REGION['prod-doenet-redirect-www']='us-east-1' +STACK_REGION['prod-doenet-redirect-beta']='us-east-1' declare -g -a STACK_ORDER STACK_ORDER=( @@ -42,8 +42,8 @@ STACK_ORDER=( prod-doenet-media-cert prod-doenet-media-cdn prod-doenet-frontend - prod-doenet-redirect - prod-doenet-www-redirect + prod-doenet-redirect-www + prod-doenet-redirect-beta ) declare -g -a SAM_STACKS