Skip to content

Instantly share code, notes, and snippets.

@syed
Created June 5, 2026 13:09
Show Gist options
  • Select an option

  • Save syed/828463b8ebf3073020d098c79065e0b0 to your computer and use it in GitHub Desktop.

Select an option

Save syed/828463b8ebf3073020d098c79065e0b0 to your computer and use it in GitHub Desktop.
ACM certificate issuance perf test tool
package main
import (
"context"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"log"
"os"
"strings"
"sync"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/acm"
acmtypes "github.com/aws/aws-sdk-go-v2/service/acm/types"
"github.com/aws/aws-sdk-go-v2/service/route53"
r53types "github.com/aws/aws-sdk-go-v2/service/route53/types"
)
type stepTimings struct {
requestCert time.Duration
waitValidation time.Duration
createDNS time.Duration
waitIssuance time.Duration
retrieveCert time.Duration
total time.Duration
}
type runResult struct {
id int
domain string
timings stepTimings
err error
}
func main() {
domain := flag.String("domain", "", "Domain name for the certificate (required)")
hostedZoneID := flag.String("hosted-zone-id", "", "Route 53 hosted zone ID (required)")
region := flag.String("region", "us-east-1", "AWS region")
pollInterval := flag.Duration("poll-interval", 10*time.Second, "Polling interval for certificate status")
timeout := flag.Duration("timeout", 30*time.Minute, "Timeout waiting for certificate issuance")
cleanup := flag.Bool("cleanup", false, "Delete certificate and DNS record after completion")
parallel := flag.Int("parallel", 1, "Number of parallel certificate requests")
flag.Parse()
if *domain == "" || *hostedZoneID == "" {
flag.Usage()
os.Exit(1)
}
ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(*region))
if err != nil {
log.Fatalf("unable to load AWS config: %v", err)
}
acmClient := acm.NewFromConfig(cfg)
r53Client := route53.NewFromConfig(cfg)
if *parallel == 1 {
result := runSingle(ctx, acmClient, r53Client, 0, *domain, *hostedZoneID, *pollInterval, *timeout, *cleanup, true)
if result.err != nil {
log.Fatalf("certificate run failed: %v", result.err)
}
printSingleSummary(result)
return
}
// Parallel mode
fmt.Printf("=== Starting %d parallel certificate requests ===\n\n", *parallel)
overallStart := time.Now()
var wg sync.WaitGroup
results := make([]runResult, *parallel)
for i := 0; i < *parallel; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
d := fmt.Sprintf("cert-test-%d.%s", id, strings.TrimPrefix(*domain, "cert-test."))
if !strings.HasPrefix(*domain, "cert-test.") {
d = fmt.Sprintf("cert-test-%d.%s", id, *domain)
}
results[id] = runSingle(ctx, acmClient, r53Client, id, d, *hostedZoneID, *pollInterval, *timeout, *cleanup, false)
}(i)
}
wg.Wait()
overallDuration := time.Since(overallStart)
printParallelSummary(results, overallDuration)
}
func runSingle(ctx context.Context, acmClient *acm.Client, r53Client *route53.Client, id int, domain, hostedZoneID string, pollInterval, timeout time.Duration, cleanup, verbose bool) runResult {
prefix := fmt.Sprintf("[worker-%d]", id)
logf := func(format string, args ...interface{}) {
if verbose {
fmt.Printf(format, args...)
} else {
fmt.Printf(prefix+" "+format, args...)
}
}
var t stepTimings
overallStart := time.Now()
// Step 1: Request certificate
logf("Requesting certificate for %s\n", domain)
reqStart := time.Now()
reqOutput, err := acmClient.RequestCertificate(ctx, &acm.RequestCertificateInput{
DomainName: &domain,
ValidationMethod: acmtypes.ValidationMethodDns,
Options: &acmtypes.CertificateOptions{
CertificateTransparencyLoggingPreference: acmtypes.CertificateTransparencyLoggingPreferenceEnabled,
},
})
if err != nil {
return runResult{id: id, domain: domain, err: fmt.Errorf("request certificate: %w", err)}
}
certArn := *reqOutput.CertificateArn
t.requestCert = time.Since(reqStart)
logf(" ARN: %s (%s)\n", certArn, t.requestCert.Round(time.Millisecond))
if cleanup {
defer cleanupResources(ctx, acmClient, r53Client, certArn, hostedZoneID, prefix, verbose)
}
// Step 2: Wait for DNS validation details
logf(" Waiting for validation details...\n")
dnsWaitStart := time.Now()
var validationRecord acmtypes.ResourceRecord
for {
descOutput, err := acmClient.DescribeCertificate(ctx, &acm.DescribeCertificateInput{
CertificateArn: &certArn,
})
if err != nil {
return runResult{id: id, domain: domain, err: fmt.Errorf("describe certificate: %w", err)}
}
if len(descOutput.Certificate.DomainValidationOptions) > 0 &&
descOutput.Certificate.DomainValidationOptions[0].ResourceRecord != nil {
validationRecord = *descOutput.Certificate.DomainValidationOptions[0].ResourceRecord
break
}
time.Sleep(2 * time.Second)
}
t.waitValidation = time.Since(dnsWaitStart)
logf(" Validation details ready (%s)\n", t.waitValidation.Round(time.Millisecond))
// Step 3: Create Route 53 DNS record
r53Start := time.Now()
_, err = r53Client.ChangeResourceRecordSets(ctx, &route53.ChangeResourceRecordSetsInput{
HostedZoneId: &hostedZoneID,
ChangeBatch: &r53types.ChangeBatch{
Changes: []r53types.Change{
{
Action: r53types.ChangeActionUpsert,
ResourceRecordSet: &r53types.ResourceRecordSet{
Name: validationRecord.Name,
Type: r53types.RRTypeCname,
TTL: aws.Int64(300),
ResourceRecords: []r53types.ResourceRecord{
{Value: validationRecord.Value},
},
},
},
},
},
})
if err != nil {
return runResult{id: id, domain: domain, err: fmt.Errorf("create route53 record: %w", err)}
}
t.createDNS = time.Since(r53Start)
logf(" DNS record created (%s)\n", t.createDNS.Round(time.Millisecond))
// Step 4: Wait for certificate issuance
logf(" Waiting for issuance...\n")
issueStart := time.Now()
deadline := time.After(timeout)
var certStatus acmtypes.CertificateStatus
for {
select {
case <-deadline:
return runResult{id: id, domain: domain, err: fmt.Errorf("timeout after %s (last status: %s)", timeout, certStatus)}
default:
}
descOutput, err := acmClient.DescribeCertificate(ctx, &acm.DescribeCertificateInput{
CertificateArn: &certArn,
})
if err != nil {
return runResult{id: id, domain: domain, err: fmt.Errorf("describe certificate: %w", err)}
}
certStatus = descOutput.Certificate.Status
if certStatus == acmtypes.CertificateStatusIssued {
break
}
if certStatus == acmtypes.CertificateStatusFailed {
return runResult{id: id, domain: domain, err: fmt.Errorf("certificate failed: %s", descOutput.Certificate.FailureReason)}
}
time.Sleep(pollInterval)
}
t.waitIssuance = time.Since(issueStart)
logf(" Certificate ISSUED (%s)\n", t.waitIssuance.Round(time.Millisecond))
// Step 5: Retrieve certificate
getStart := time.Now()
getCertOutput, err := acmClient.GetCertificate(ctx, &acm.GetCertificateInput{
CertificateArn: &certArn,
})
if err != nil {
return runResult{id: id, domain: domain, err: fmt.Errorf("get certificate: %w", err)}
}
t.retrieveCert = time.Since(getStart)
t.total = time.Since(overallStart)
if verbose {
fmt.Println("--- Certificate ---")
fmt.Println(*getCertOutput.Certificate)
if getCertOutput.CertificateChain != nil {
fmt.Println("--- Certificate Chain ---")
fmt.Println(*getCertOutput.CertificateChain)
}
printCertDetails(*getCertOutput.Certificate)
}
logf(" Retrieved certificate (%s) | Total: %s\n", t.retrieveCert.Round(time.Millisecond), t.total.Round(time.Millisecond))
return runResult{id: id, domain: domain, timings: t}
}
func printSingleSummary(r runResult) {
t := r.timings
fmt.Println("\n========================================")
fmt.Println(" TIMING SUMMARY")
fmt.Println("========================================")
fmt.Printf(" %-30s %s\n", "Request certificate", t.requestCert.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "Wait for validation details", t.waitValidation.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "Create Route 53 record", t.createDNS.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "Wait for issuance", t.waitIssuance.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "Retrieve certificate", t.retrieveCert.Round(time.Millisecond))
fmt.Println(" ----------------------------------------")
fmt.Printf(" %-30s %s\n", "TOTAL (request to issuance)", t.total.Round(time.Millisecond))
fmt.Println("========================================")
}
func printParallelSummary(results []runResult, overallDuration time.Duration) {
fmt.Println("\n========================================")
fmt.Println(" PER-WORKER RESULTS")
fmt.Println("========================================")
var succeeded []runResult
for _, r := range results {
if r.err != nil {
fmt.Printf(" worker-%d %-50s FAILED: %v\n", r.id, r.domain, r.err)
} else {
fmt.Printf(" worker-%d %-50s %s\n", r.id, r.domain, r.timings.total.Round(time.Millisecond))
succeeded = append(succeeded, r)
}
}
if len(succeeded) == 0 {
fmt.Println("\n No successful runs to average.")
return
}
var avg stepTimings
for _, r := range succeeded {
avg.requestCert += r.timings.requestCert
avg.waitValidation += r.timings.waitValidation
avg.createDNS += r.timings.createDNS
avg.waitIssuance += r.timings.waitIssuance
avg.retrieveCert += r.timings.retrieveCert
avg.total += r.timings.total
}
n := time.Duration(len(succeeded))
avg.requestCert /= n
avg.waitValidation /= n
avg.createDNS /= n
avg.waitIssuance /= n
avg.retrieveCert /= n
avg.total /= n
var minTotal, maxTotal time.Duration
minTotal = succeeded[0].timings.total
maxTotal = succeeded[0].timings.total
for _, r := range succeeded[1:] {
if r.timings.total < minTotal {
minTotal = r.timings.total
}
if r.timings.total > maxTotal {
maxTotal = r.timings.total
}
}
fmt.Println("\n========================================")
fmt.Printf(" AVERAGE TIMINGS (%d/%d succeeded)\n", len(succeeded), len(results))
fmt.Println("========================================")
fmt.Printf(" %-30s %s\n", "Request certificate", avg.requestCert.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "Wait for validation details", avg.waitValidation.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "Create Route 53 record", avg.createDNS.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "Wait for issuance", avg.waitIssuance.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "Retrieve certificate", avg.retrieveCert.Round(time.Millisecond))
fmt.Println(" ----------------------------------------")
fmt.Printf(" %-30s %s\n", "AVG total per cert", avg.total.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "MIN total", minTotal.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "MAX total", maxTotal.Round(time.Millisecond))
fmt.Printf(" %-30s %s\n", "Wall clock (all parallel)", overallDuration.Round(time.Millisecond))
fmt.Println("========================================")
}
func printCertDetails(certPEM string) {
block, _ := pem.Decode([]byte(certPEM))
if block == nil {
return
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return
}
fmt.Println("\n--- Certificate Details ---")
fmt.Printf(" Subject: %s\n", cert.Subject.CommonName)
fmt.Printf(" Issuer: %s\n", cert.Issuer.CommonName)
fmt.Printf(" Serial: %s\n", cert.SerialNumber)
fmt.Printf(" Not Before: %s\n", cert.NotBefore)
fmt.Printf(" Not After: %s\n", cert.NotAfter)
if len(cert.DNSNames) > 0 {
fmt.Printf(" SANs: %s\n", strings.Join(cert.DNSNames, ", "))
}
}
func cleanupResources(ctx context.Context, acmClient *acm.Client, r53Client *route53.Client, certArn, hostedZoneID, prefix string, verbose bool) {
logf := func(format string, args ...interface{}) {
if verbose {
fmt.Printf(format, args...)
} else {
fmt.Printf(prefix+" "+format, args...)
}
}
logf("Cleanup: deleting cert %s\n", certArn)
descOutput, err := acmClient.DescribeCertificate(ctx, &acm.DescribeCertificateInput{
CertificateArn: &certArn,
})
if err == nil && len(descOutput.Certificate.DomainValidationOptions) > 0 {
rr := descOutput.Certificate.DomainValidationOptions[0].ResourceRecord
if rr != nil {
_, err = r53Client.ChangeResourceRecordSets(ctx, &route53.ChangeResourceRecordSetsInput{
HostedZoneId: &hostedZoneID,
ChangeBatch: &r53types.ChangeBatch{
Changes: []r53types.Change{
{
Action: r53types.ChangeActionDelete,
ResourceRecordSet: &r53types.ResourceRecordSet{
Name: rr.Name,
Type: r53types.RRTypeCname,
TTL: aws.Int64(300),
ResourceRecords: []r53types.ResourceRecord{
{Value: rr.Value},
},
},
},
},
},
})
if err != nil {
logf(" Warning: failed to delete DNS record: %v\n", err)
} else {
logf(" Deleted DNS validation record\n")
}
}
}
_, err = acmClient.DeleteCertificate(ctx, &acm.DeleteCertificateInput{
CertificateArn: &certArn,
})
if err != nil {
logf(" Warning: failed to delete certificate: %v\n", err)
} else {
logf(" Deleted certificate\n")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment