github.com/awslabs/fargate@v0.2.3/acm/certificate.go (about)

     1  package acm
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	awsacm "github.com/aws/aws-sdk-go/service/acm"
    11  	"github.com/jpignata/fargate/console"
    12  	"golang.org/x/time/rate"
    13  )
    14  
    15  type Certificate struct {
    16  	Arn                     string
    17  	Status                  string
    18  	SubjectAlternativeNames []string
    19  	DomainName              string
    20  	Validations             []CertificateValidation
    21  	Type                    string
    22  }
    23  
    24  func (c *Certificate) AddValidation(v CertificateValidation) {
    25  	c.Validations = append(c.Validations, v)
    26  }
    27  
    28  func (c *Certificate) Inflate(d *awsacm.CertificateDetail) *Certificate {
    29  	c.Status = aws.StringValue(d.Status)
    30  	c.SubjectAlternativeNames = aws.StringValueSlice(d.SubjectAlternativeNames)
    31  	c.Type = aws.StringValue(d.Type)
    32  
    33  	for _, domainValidation := range d.DomainValidationOptions {
    34  		validation := CertificateValidation{
    35  			Status:     aws.StringValue(domainValidation.ValidationStatus),
    36  			DomainName: aws.StringValue(domainValidation.DomainName),
    37  		}
    38  
    39  		if domainValidation.ResourceRecord != nil {
    40  			validation.ResourceRecord = CertificateResourceRecord{
    41  				Type:  aws.StringValue(domainValidation.ResourceRecord.Type),
    42  				Name:  aws.StringValue(domainValidation.ResourceRecord.Name),
    43  				Value: aws.StringValue(domainValidation.ResourceRecord.Value),
    44  			}
    45  		}
    46  
    47  		c.AddValidation(validation)
    48  	}
    49  
    50  	return c
    51  }
    52  
    53  func (c *Certificate) IsIssued() bool {
    54  	return c.Status == awsacm.CertificateStatusIssued
    55  }
    56  
    57  type CertificateValidation struct {
    58  	Status         string
    59  	DomainName     string
    60  	ResourceRecord CertificateResourceRecord
    61  }
    62  
    63  func (v *CertificateValidation) IsPendingValidation() bool {
    64  	return v.Status == awsacm.DomainStatusPendingValidation
    65  }
    66  
    67  func (v *CertificateValidation) IsSuccess() bool {
    68  	return v.Status == awsacm.DomainStatusSuccess
    69  }
    70  
    71  func (v *CertificateValidation) IsFailed() bool {
    72  	return v.Status == awsacm.DomainStatusFailed
    73  }
    74  
    75  func (v *CertificateValidation) ResourceRecordString() string {
    76  	if v.ResourceRecord.Type == "" {
    77  		return ""
    78  	}
    79  
    80  	return fmt.Sprintf("%s %s -> %s",
    81  		v.ResourceRecord.Type,
    82  		v.ResourceRecord.Name,
    83  		v.ResourceRecord.Value,
    84  	)
    85  }
    86  
    87  type CertificateResourceRecord struct {
    88  	Type  string
    89  	Name  string
    90  	Value string
    91  }
    92  
    93  func (c *Certificate) IsPendingValidation() bool {
    94  	return c.Status == awsacm.CertificateStatusPendingValidation
    95  }
    96  
    97  func ValidateDomainName(domainName string) error {
    98  	if len(domainName) < 1 || len(domainName) > 253 {
    99  		return fmt.Errorf("The domain name must be between 1 and 253 characters in length")
   100  	}
   101  
   102  	if strings.Count(domainName, ".") > 62 {
   103  		return fmt.Errorf("The domain name cannot exceed 63 octets")
   104  	}
   105  
   106  	if strings.Count(domainName, ".") == 0 {
   107  		return fmt.Errorf("The domain name requires at least 2 octets")
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  func ValidateAlias(domainName string) error {
   114  	if len(domainName) < 1 || len(domainName) > 253 {
   115  		return fmt.Errorf("The alias domain name must be between 1 and 253 characters in length")
   116  	}
   117  
   118  	if strings.Count(domainName, ".") > 252 {
   119  		return fmt.Errorf("The alias domain name cannot exceed 253 octets")
   120  	}
   121  
   122  	if strings.Count(domainName, ".") == 0 {
   123  		return fmt.Errorf("The alias domain name requires at least 2 octets")
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  func (acm *ACM) RequestCertificate(domainName string, aliases []string) {
   130  	console.Debug("Requesting ACM certificate")
   131  
   132  	requestCertificateInput := &awsacm.RequestCertificateInput{
   133  		DomainName:       aws.String(domainName),
   134  		ValidationMethod: aws.String(awsacm.ValidationMethodDns),
   135  	}
   136  
   137  	if len(aliases) > 0 {
   138  		requestCertificateInput.SetSubjectAlternativeNames(aws.StringSlice(aliases))
   139  	}
   140  
   141  	_, err := acm.svc.RequestCertificate(requestCertificateInput)
   142  
   143  	if err != nil {
   144  		console.ErrorExit(err, "Couldn't request ACM certificate")
   145  	}
   146  }
   147  
   148  func (acm *ACM) ListCertificates() []*Certificate {
   149  	var wg sync.WaitGroup
   150  
   151  	ctx := context.Background()
   152  	ch := make(chan *Certificate)
   153  	certificates := acm.listCertificates()
   154  	limiter := rate.NewLimiter(10, 1)
   155  
   156  	for i := 0; i < 4; i++ {
   157  		wg.Add(1)
   158  
   159  		go func() {
   160  			defer wg.Done()
   161  
   162  			for c := range ch {
   163  				if err := limiter.Wait(ctx); err == nil {
   164  					certificateDetail := acm.describeCertificate(c.Arn)
   165  					c.Inflate(certificateDetail)
   166  				}
   167  			}
   168  		}()
   169  	}
   170  
   171  	for _, c := range certificates {
   172  		ch <- c
   173  	}
   174  
   175  	close(ch)
   176  
   177  	wg.Wait()
   178  
   179  	return certificates
   180  }
   181  
   182  func (acm *ACM) DescribeCertificate(domainName string) *Certificate {
   183  	var certificate *Certificate
   184  
   185  	for _, c := range acm.listCertificates() {
   186  		if c.DomainName == domainName {
   187  			certificateDetail := acm.describeCertificate(c.Arn)
   188  			certificate = c.Inflate(certificateDetail)
   189  
   190  			break
   191  		}
   192  	}
   193  
   194  	if certificate == nil {
   195  		err := fmt.Errorf("Could not find ACM certificate for %s", domainName)
   196  		console.ErrorExit(err, "Couldn't describe ACM certificate")
   197  	}
   198  
   199  	return certificate
   200  }
   201  
   202  func (acm *ACM) ListCertificateDomainNames(certificateArns []string) []string {
   203  	var domainNames []string
   204  
   205  	for _, certificate := range acm.listCertificates() {
   206  		for _, certificateArn := range certificateArns {
   207  			if certificate.Arn == certificateArn {
   208  				domainNames = append(domainNames, certificate.DomainName)
   209  			}
   210  		}
   211  	}
   212  
   213  	return domainNames
   214  }
   215  
   216  func (acm *ACM) ImportCertificate(certificate, privateKey, certificateChain []byte) {
   217  	console.Debug("Importing ACM certificate")
   218  
   219  	input := &awsacm.ImportCertificateInput{
   220  		Certificate: certificate,
   221  		PrivateKey:  privateKey,
   222  	}
   223  
   224  	if len(certificateChain) != 0 {
   225  		input.SetCertificateChain(certificateChain)
   226  	}
   227  
   228  	_, err := acm.svc.ImportCertificate(input)
   229  
   230  	if err != nil {
   231  		console.ErrorExit(err, "Couldn't import certificate")
   232  	}
   233  }
   234  
   235  func (acm *ACM) DeleteCertificate(domainName string) {
   236  	var err error
   237  
   238  	certificates := acm.listCertificates()
   239  
   240  	for _, certificate := range certificates {
   241  		if certificate.DomainName == domainName {
   242  			_, err := acm.svc.DeleteCertificate(
   243  				&awsacm.DeleteCertificateInput{
   244  					CertificateArn: aws.String(certificate.Arn),
   245  				},
   246  			)
   247  
   248  			if err != nil {
   249  				console.ErrorExit(err, "Couldn't destroy certificate")
   250  			}
   251  
   252  			return
   253  		}
   254  	}
   255  
   256  	err = fmt.Errorf("Certificate for %s not found", domainName)
   257  	console.ErrorExit(err, "Couldn't destroy certificate")
   258  }
   259  
   260  func (acm *ACM) describeCertificate(arn string) *awsacm.CertificateDetail {
   261  	resp, err := acm.svc.DescribeCertificate(
   262  		&awsacm.DescribeCertificateInput{
   263  			CertificateArn: aws.String(arn),
   264  		},
   265  	)
   266  
   267  	if err != nil {
   268  		console.ErrorExit(err, "Couldn't describe ACM certificate")
   269  	}
   270  
   271  	return resp.Certificate
   272  }
   273  
   274  func (acm *ACM) listCertificates() []*Certificate {
   275  	certificates := []*Certificate{}
   276  
   277  	err := acm.svc.ListCertificatesPagesWithContext(
   278  		context.Background(),
   279  		&awsacm.ListCertificatesInput{},
   280  		func(resp *awsacm.ListCertificatesOutput, lastPage bool) bool {
   281  			for _, c := range resp.CertificateSummaryList {
   282  				certificates = append(
   283  					certificates,
   284  					&Certificate{
   285  						Arn:        aws.StringValue(c.CertificateArn),
   286  						DomainName: aws.StringValue(c.DomainName),
   287  					},
   288  				)
   289  			}
   290  
   291  			return true
   292  		},
   293  	)
   294  
   295  	if err != nil {
   296  		console.ErrorExit(err, "Could not list ACM certificates")
   297  	}
   298  
   299  	return certificates
   300  }