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 }