DEV Community

Sam
Sam

Posted on

2

Request ACM certificate with DNS validation in Go

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

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More