Skip to content

Commit

Permalink
fix!: common structs, readability (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevholmes authored Dec 6, 2023
1 parent e2c3df1 commit 05b7b50
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 151 deletions.
3 changes: 2 additions & 1 deletion Pulumi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ config:
github:owner: kevholmes
# web project configs
elyclover.com-infra:ghAppSrcRepo: "elyclover.com"
elyclover.com-infra:AzSubScriptionIdWeb: df058eb7-0193-43d2-b1ff-937439336e86
#elyclover.com-infra:AzSubScriptionIdWeb: df058eb7-0193-43d2-b1ff-937439336e86
elyclover.com-infra:AzTenantId: 6088cb1f-43c7-4299-9378-4946a45e93d1
elyclover.com-infra:siteKey: elyclover
elyclover.com-infra:siteName: elyclover.com
# dns configs from external Resource Group
elyclover.com-infra:dnsResourceGroup: root
elyclover.com-infra:dnsZoneName: elyclover.com
elyclover.com-infra:dnsRecordTTL: 300
66 changes: 36 additions & 30 deletions cdn.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
legacycdn "github.com/pulumi/pulumi-azure/sdk/v5/go/azure/cdn"
"github.com/pulumi/pulumi-azuread/sdk/v5/go/azuread"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)

type epCfgs struct {
Expand All @@ -18,28 +17,29 @@ type epCfgs struct {
domainArgs legacycdn.EndpointCustomDomainArgs
}

func createCdnProfile(ctx *pulumi.Context, cdnName string, azureRg pulumi.StringOutput) (profile *nativecdn.Profile, err error) {
func (pr *projectResources) createCdnProfile() (err error) {
var cdnProfileArgs = nativecdn.ProfileArgs{
Location: pulumi.String("global"),
ResourceGroupName: azureRg,
ResourceGroupName: pr.webResourceGrp.Name,
Sku: &nativecdn.SkuArgs{
Name: pulumi.String("Standard_Microsoft"),
},
}
profile, err = nativecdn.NewProfile(ctx, cdnName, &cdnProfileArgs)
cdnName := pr.cfgKeys.siteKey + pr.cfgKeys.envKey
pr.webCdnProfile, err = nativecdn.NewProfile(pr.pulumiCtx, cdnName, &cdnProfileArgs)
if err != nil {
fmt.Printf("ERROR: creating cdnProfile %s failed\n", cdnName)
return nil, err
return err
}
return
}

func createCdnEndpoint(ctx *pulumi.Context, epName string, cdnProfile *nativecdn.Profile, azureRg pulumi.StringOutput, origin pulumi.StringOutput) (ep *nativecdn.Endpoint, err error) {
func (pr *projectResources) createCdnEndpoint() (err error) {
// Create CDN Endpoint using newly created CDN Profile
originsArgs := nativecdn.DeepCreatedOriginArray{
nativecdn.DeepCreatedOriginArgs{
Enabled: pulumi.Bool(true),
HostName: origin,
HostName: pr.webStaticEp,
Name: pulumi.String("origin1"),
}}
// set up single delivery rule which forwards all HTTP traffic to HTTPS on CDN endpoint
Expand Down Expand Up @@ -74,9 +74,9 @@ func createCdnEndpoint(ctx *pulumi.Context, epName string, cdnProfile *nativecdn
}
cdnEndPointArgs := nativecdn.EndpointArgs{
Origins: originsArgs,
ProfileName: cdnProfile.Name,
ResourceGroupName: azureRg,
OriginHostHeader: origin,
ProfileName: pr.webCdnProfile.Name,
ResourceGroupName: pr.webResourceGrp.Name,
OriginHostHeader: pr.webStaticEp,
IsHttpAllowed: pulumi.Bool(true),
IsHttpsAllowed: pulumi.Bool(true),
DeliveryPolicy: deliveryPolicy,
Expand All @@ -93,16 +93,16 @@ func createCdnEndpoint(ctx *pulumi.Context, epName string, cdnProfile *nativecdn
pulumi.String("image/svg+xml"),
},
}
ep, err = nativecdn.NewEndpoint(ctx, epName, &cdnEndPointArgs)
epName := pr.cfgKeys.siteKey + pr.cfgKeys.envKey
pr.webCdnEp, err = nativecdn.NewEndpoint(pr.pulumiCtx, epName, &cdnEndPointArgs)
if err != nil {
fmt.Printf("ERROR: creating endpoint %s failed\n", epName)
return ep, err
return err
}
return
}

func newEndpointCustomDomain(ctx *pulumi.Context, epdName string, endpoint *nativecdn.Endpoint, domain pulumi.StringOutput,
cfg *config.Config) (epd *legacycdn.EndpointCustomDomain, err error) {
func (pr *projectResources) newEndpointCustomDomain() (err error) {
// Utilize the azure legacy provider since it supports setting up auto-TLS for CDN custom domains
// azure-native provider strangely lacks support for CDN-managed TLS on custom domains...
epCfg := epCfgs{
Expand All @@ -115,17 +115,19 @@ func newEndpointCustomDomain(ctx *pulumi.Context, epdName string, endpoint *nati
TlsVersion: pulumi.String("TLS12"),
},
domainArgs: legacycdn.EndpointCustomDomainArgs{
CdnEndpointId: endpoint.ID(),
HostName: domain,
CdnEndpointId: pr.webCdnEp.ID(),
HostName: pr.webFqdn,
},
}
switch ctx.Stack() {

switch pr.cfgKeys.envKey {
// prod is byo certificate (self-managed)
case "prod":
// all subdomains are ACME and managed by Azure (mostly)
case PROD:
// import pfx certificate stored at rest in source control to Azure Key Vault
certSec, err := importPfxToKeyVault(ctx, cfg)
certSec, err := importPfxToKeyVault(pr.pulumiCtx, pr.cfg)
if err != nil {
return epd, err
return err
}
epCfg.userManaged.KeyVaultSecretId = certSec.Properties.SecretUri()
epCfg.domainArgs.UserManagedHttps = epCfg.userManaged
Expand All @@ -139,10 +141,13 @@ func newEndpointCustomDomain(ctx *pulumi.Context, epdName string, endpoint *nati
// a feeling it's due to mix/match of azure and azure-native provider for our CDN work. By adding it we
// avoid a constant cycle of Pulumi trying to destroy and re-create the Custom Domain which causes other
// issues due to the reliance on the CNAME record which the provider does not (appear?) pick up on, unfortunately.
epd, err = legacycdn.NewEndpointCustomDomain(ctx, epdName, &epCfg.domainArgs, pulumi.IgnoreChanges([]string{"cdnEndpointId"}))
// https://github.com/kevholmes/elyclover.com-infra/issues/76
_, err = legacycdn.NewEndpointCustomDomain(
pr.pulumiCtx, pr.cfgKeys.siteKey+pr.cfgKeys.envKey, &epCfg.domainArgs,
pulumi.IgnoreChanges([]string{"cdnEndpointId"}))
if err != nil {
fmt.Println("ERROR: creating custom domain for CDN endpoint failed")
return epd, err
return err
}

return
Expand All @@ -152,24 +157,25 @@ func newEndpointCustomDomain(ctx *pulumi.Context, epdName string, endpoint *nati
// Production uses an apex domain (eg tld.com) which Azure doesn't support free TLS certs + rotation on (:shrug:)
// so we'll need to set up a Service Principal registered under the Azure CDN App profile and give it RBAC access to
// an external Azure KeyVault resource that contains our pfx certificate needed for the prod tld.com domain.
func setupTlsTermination(ctx *pulumi.Context, cfg *config.Config, ep *nativecdn.Endpoint, fqdn pulumi.StringOutput) (err error) {
if ctx.Stack() == "prod" {
func (pr *projectResources) setupTlsTermination() (err error) {
if pr.cfgKeys.envKey == PROD {
// Register Azure CDN Application as Service Principal in AD/Entra tenant so it can fetch TLS pfx data in external Keystore
cdnId := cfg.Require("Microsoft.AzureFrontDoor-Cdn")
nsp, err := azuread.NewServicePrincipal(ctx, cdnId, &azuread.ServicePrincipalArgs{
ApplicationId: pulumi.String(cdnId),
nsp, err := azuread.NewServicePrincipal(pr.pulumiCtx, pr.cfgKeys.cdnAzureId, &azuread.ServicePrincipalArgs{
ApplicationId: pulumi.String(pr.cfgKeys.cdnAzureId),
UseExisting: pulumi.Bool(false),
Description: pulumi.String("Service Principal tied to built-in Azure CDN/FD Application ID/product"),
})
if err != nil {
return err
}

// assign predefined "Key Vault Secret User" RoleDefinitionId to Service Principal we just created
// https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#key-vault-secrets-user
// This allows Azure CDN to access the pfx keys we purchase/generate external to pulumi (quite a bit cheaper than asking Azure to do it.)
keyVaultScope := pulumi.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.KeyVault/vaults/%s",
cfg.Require("keyVaultAzureSubscription"), cfg.Require("keyvaultResourceGroup"), cfg.Require("keyVaultName"))
_, err = authorization.NewRoleAssignment(ctx, "AzureFDCDNreadKVCerts", &authorization.RoleAssignmentArgs{
pr.cfgKeys.kvAzureSubscription, pr.cfgKeys.kvAzureResourceGrp, pr.cfgKeys.kvAzureName)

_, err = authorization.NewRoleAssignment(pr.pulumiCtx, "AzureFDCDNreadKVCerts", &authorization.RoleAssignmentArgs{
PrincipalId: nsp.ID(),
PrincipalType: pulumi.String("ServicePrincipal"),
RoleDefinitionId: pulumi.String("/providers/Microsoft.Authorization/roleDefinitions/4633458b-17de-408a-b874-0445c86b69e6"),
Expand All @@ -181,7 +187,7 @@ func setupTlsTermination(ctx *pulumi.Context, cfg *config.Config, ep *nativecdn.
}

// Add Custom Domain to CDN for prod or non-prod
_, err = newEndpointCustomDomain(ctx, cfg.Require("siteKey")+ctx.Stack(), ep, fqdn, cfg)
err = pr.newEndpointCustomDomain()
if err != nil {
return err
}
Expand Down
85 changes: 49 additions & 36 deletions dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,47 @@ package main

import (
"fmt"
"strconv"
"strings"

"github.com/pulumi/pulumi-azure-native-sdk/cdn/v2"
"github.com/pulumi/pulumi-azure/sdk/v5/go/azure/dns"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func lookupDnsZone(ctx *pulumi.Context, rg string, lz string) (lzResult *dns.LookupZoneResult, err error) {
func (pr *projectResources) lookupDnsZone() (err error) {
dnsLookupZoneArgs := dns.LookupZoneArgs{
Name: lz,
ResourceGroupName: &rg,
Name: pr.cfgKeys.dnsLookupZone,
ResourceGroupName: &pr.cfgKeys.dnsResourceGrp,
}
lzResult, err = dns.LookupZone(ctx, &dnsLookupZoneArgs)
pr.webDnsZone, err = dns.LookupZone(pr.pulumiCtx, &dnsLookupZoneArgs)
if err != nil {
fmt.Printf("ERROR: looking up dnsZone in RG %s failed\n", rg)
return lzResult, err
fmt.Printf("ERROR: looking up dnsZone in RG %s failed\n", pr.cfgKeys.dnsResourceGrp)
return err
}
return
}

func createDnsRecordByEnv(ctx *pulumi.Context, dnsRG string, dz *dns.LookupZoneResult, ep *cdn.Endpoint, envKey string, siteKey string) (d pulumi.StringOutput, err error) {
func (pr *projectResources) createDnsRecordByEnv() (err error) {
fqdnErr := fmt.Errorf("passed FQDN string didn't include trailing '.' did the Azure API change?")
switch envKey {
case "prod": // apex domain for prod eg tld.com uses A record referencing Azure resource
switch pr.cfgKeys.envKey {
case PROD: // apex domain for prod eg tld.com uses A record referencing Azure resource
// create A record pointing at CDN Endpoint resource ID
dnsRecord, err := createARecordPointingAtCdnResourceID(ctx, dnsRG, dz, pulumi.StringOutput(ep.ID()), envKey, siteKey)
err = pr.createApexRecordPointingAtCdnResourceID()
if err != nil {
return d, err
return err
}
// create CNAME 'cdnverify.tld.com' record
cdnVerify := "cdnverify"
cdnVerifyHostname := ep.HostName.ApplyT(func(h string) (r string) {
cdnVerifyHostname := pr.webCdnEp.HostName.ApplyT(func(h string) (r string) {
r = cdnVerify + "." + h
return
}).(pulumi.StringOutput)
_, err = createCNAMERecordPointingAtCdnEndpoint(ctx, dnsRG, dz, cdnVerifyHostname, cdnVerify, siteKey)
err = pr.createCNAMERecordPointingAtCdnEndpoint(cdnVerifyHostname, pr.cfgKeys.siteKey+cdnVerify)
if err != nil {
return d, err
return err
}
// strip out trailing '.' from A record returned FQDN string within Azure DNS API
d = dnsRecord.Fqdn.ApplyT(func(fqdn string) (string, error) {
pr.webFqdn = pr.dnsRecords.a.Fqdn.ApplyT(func(fqdn string) (string, error) {
h, found := strings.CutSuffix(fqdn, ".")
if !found {
return h, fqdnErr
Expand All @@ -51,12 +51,12 @@ func createDnsRecordByEnv(ctx *pulumi.Context, dnsRG string, dz *dns.LookupZoneR
}).(pulumi.StringOutput)
default: // everything that's not prod and has a sub-domain eg dev.tld.com
// create CNAME DNS record to point at CDN endpoint
dnsRecord, err := createCNAMERecordPointingAtCdnEndpoint(ctx, dnsRG, dz, ep.HostName, envKey, siteKey)
err = pr.createCNAMERecordPointingAtCdnEndpoint(pr.webCdnEp.HostName, pr.cfgKeys.envKey)
if err != nil {
return d, err
return err
}
// strip out trailing '.' from CNAME's returned FQDN string within Azure DNS API
d = dnsRecord.Fqdn.ApplyT(func(fqdn string) (string, error) {
pr.webFqdn = pr.dnsRecords.cname.Fqdn.ApplyT(func(fqdn string) (string, error) {
h, found := strings.CutSuffix(fqdn, ".")
if !found {
return h, fqdnErr
Expand All @@ -67,37 +67,50 @@ func createDnsRecordByEnv(ctx *pulumi.Context, dnsRG string, dz *dns.LookupZoneR
return
}

func createCNAMERecordPointingAtCdnEndpoint(ctx *pulumi.Context, dnsRG string, dz *dns.LookupZoneResult, ep pulumi.StringOutput, envKey string, siteKey string) (record *dns.CNameRecord, err error) {
func (pr *projectResources) createCNAMERecordPointingAtCdnEndpoint(ep pulumi.StringOutput, name string) (err error) {
ttl, err := strconv.Atoi(pr.cfgKeys.dnsRecordTTL)
if err != nil {
fmt.Printf("ERROR: dnsRecordTTL provided cannot be converted from string to int\n")
return err
}
// create new CNAME record in zone for non-prod env that will be used by CDN endpoint
dnsRecordArgs := dns.CNameRecordArgs{
ZoneName: pulumi.String(dz.Name),
ResourceGroupName: pulumi.String(dnsRG),
Ttl: pulumi.Int(300), // 5 minutes
Name: pulumi.String(envKey),
ZoneName: pulumi.String(pr.webDnsZone.Name),
ResourceGroupName: pulumi.String(pr.cfgKeys.dnsResourceGrp),
Ttl: pulumi.Int(ttl),
Name: pulumi.String(name),
Record: ep,
}
record, err = dns.NewCNameRecord(ctx, siteKey+envKey, &dnsRecordArgs)

pr.dnsRecords.cname, err = dns.NewCNameRecord(pr.pulumiCtx, name, &dnsRecordArgs)
if err != nil {
fmt.Printf("ERROR: creating CNAME record in RG %s failed\n",
dnsRG)
return record, err
pr.cfgKeys.dnsResourceGrp)
return err
}
return
}

func createARecordPointingAtCdnResourceID(ctx *pulumi.Context, dnsRG string, dz *dns.LookupZoneResult, tg pulumi.StringOutput, envKey string, siteKey string) (record *dns.ARecord, err error) {
func (pr *projectResources) createApexRecordPointingAtCdnResourceID() (err error) {
ttl, err := strconv.Atoi(pr.cfgKeys.dnsRecordTTL)
if err != nil {
fmt.Printf("ERROR: dnsRecordTTL provided cannot be converted from string to int\n")
return err
}
rootRecordName := "@"
dnsRecordArgs := dns.ARecordArgs{
Name: pulumi.String("@"),
ZoneName: pulumi.String(dz.Name),
ResourceGroupName: pulumi.String(dnsRG),
Ttl: pulumi.Int(300), // 5 minutes
TargetResourceId: tg,
Name: pulumi.String(rootRecordName),
ZoneName: pulumi.String(pr.webDnsZone.Name),
ResourceGroupName: pulumi.String(pr.cfgKeys.dnsResourceGrp),
Ttl: pulumi.Int(ttl),
TargetResourceId: pulumi.StringOutput(pr.webCdnEp.ID()),
}
record, err = dns.NewARecord(ctx, siteKey+envKey, &dnsRecordArgs)
name := pr.cfgKeys.siteKey + pr.cfgKeys.envKey
pr.dnsRecords.a, err = dns.NewARecord(pr.pulumiCtx, name, &dnsRecordArgs)
if err != nil {
fmt.Printf("ERROR: creating A record in RG %s failed\n",
dnsRG)
return record, err
pr.cfgKeys.dnsResourceGrp)
return err
}
return
}
Loading

0 comments on commit 05b7b50

Please sign in to comment.