Request ACM certificate with DNS validation in Go
Tags: aws, go, opensource
Published: 2022-08-18 11:03:11

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.

```golang
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

}

```


You can find the code here:

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