Request ACM certificate with DNS validation in Go

Sam - Aug 18 '22 - - Dev Community

I've been dealing with AWS SDK a lot in building https://github.com/dotenx/dotenx, and I've found it challenging quite often, particularly because of poor documentation.

One of the issues I had to resolve was requesting a TLS certificate from AWS ACM and validating it with DNS validation.

If you're in a similar situation, you can use this snippet.

func requestCertificate(domainName, hostedZoneId string) (string, error) {
    cfg := &aws.Config{
        Region: aws.String(config.Region),
    }
    svc := acm.New(session.New(), cfg)

    input := &acm.RequestCertificateInput{
        DomainName:       aws.String(domainName),
        IdempotencyToken: aws.String(strings.Replace(domainName, ".", "", -1)),
        ValidationMethod: aws.String("DNS"),
        SubjectAlternativeNames: []*string{
            aws.String("*." + domainName),
        },
        DomainValidationOptions: []*acm.DomainValidationOption{
            {
                DomainName:       aws.String(domainName),
                ValidationDomain: aws.String(domainName),
            },
        },
    }
    result, err := svc.RequestCertificate(input)
    if err != nil {
        return "", err
    }

    time.Sleep(time.Second * 10) // This MUST be long enough, o.w. the validation options won't be available

    dcIn := &acm.DescribeCertificateInput{
        CertificateArn: result.CertificateArn,
    }
    c, err := svc.DescribeCertificate(dcIn)
    if err != nil {
        return "", err
    }
    if c.Certificate.DomainValidationOptions == nil {
        errMsg := "DomainValidationOptions does not exists"
        logrus.Error(errMsg)
        return "", errors.New(errMsg)
    }

    fmt.Println("DomainValidationOptions: ", c.Certificate.DomainValidationOptions) // ---> Log at the bottom
    for _, dvo := range c.Certificate.DomainValidationOptions {
        vRecordName := dvo.ResourceRecord.Name // -----> this is nil and causes panic
        vRecordValue := dvo.ResourceRecord.Value
        createRoute53Record(*vRecordName, *vRecordValue, hostedZoneId)
    }

    return *result.CertificateArn, nil
}

func createRoute53Record(domain, value, hostedZoneId string) error {
    cfg := &aws.Config{
        Region: aws.String(config.Region),
    }
    if config.Configs.App.RunLocally {
        creds := credentials.NewStaticCredentials(config.Configs.Secrets.AwsAccessKeyId, config.Configs.Secrets.AwsSecretAccessKey, "")

        cfg = aws.NewConfig().WithRegion(config.Configs.Upload.S3Region).WithCredentials(creds)
    }
    svc := route53.New(session.New(), cfg)
    resourceRecordSet := &route53.ResourceRecordSet{
        Name: aws.String(domain + "."),
        Type: aws.String("CNAME"),
        ResourceRecords: []*route53.ResourceRecord{
            {
                Value: aws.String(value),
            },
        },
        TTL: aws.Int64(300),
    }
    upsert := []*route53.Change{{
        Action:            aws.String("UPSERT"),
        ResourceRecordSet: resourceRecordSet,
    }}

    // Put it into a pretty envelope with a stamp for route53#zoneId and change ticket
    params := route53.ChangeResourceRecordSetsInput{
        ChangeBatch: &route53.ChangeBatch{
            Changes: upsert,
        },
        HostedZoneId: aws.String(hostedZoneId),
    }

    // Post it
    _, err := svc.ChangeResourceRecordSets(&params)

    if err != nil {
        logrus.Error(err.Error())
    }
    return err

}

Enter fullscreen mode Exit fullscreen mode

You can find the code here:

https://gist.github.com/mkamrani/edf0134801076352e8c502ff28801e46

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .