PageRenderTime 68ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/api/models/ssl.go

https://gitlab.com/convox/rack
Go | 222 lines | 164 code | 51 blank | 7 comment | 40 complexity | a7e40a6478d26863fabb2737c57167e5 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause, 0BSD, LGPL-3.0
  1. package models
  2. import (
  3. "crypto/x509"
  4. "encoding/pem"
  5. "fmt"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/aws/aws-sdk-go/aws"
  11. "github.com/aws/aws-sdk-go/service/acm"
  12. "github.com/aws/aws-sdk-go/service/cloudformation"
  13. "github.com/aws/aws-sdk-go/service/iam"
  14. )
  15. type SSL struct {
  16. Certificate string `json:"certificate"`
  17. Expiration time.Time `json:"expiration"`
  18. Domain string `json:"domain"`
  19. Process string `json:"process"`
  20. Port int `json:"port"`
  21. Secure bool `json:"secure"`
  22. }
  23. type SSLs []SSL
  24. func ListSSLs(a string) (SSLs, error) {
  25. app, err := GetApp(a)
  26. if err != nil {
  27. return nil, err
  28. }
  29. ssls := make(SSLs, 0)
  30. // Find stack Parameters like WebPort443Certificate with an ARN set for the value
  31. // Get and decode corresponding certificate info
  32. re := regexp.MustCompile(`(\w+)Port(\d+)Certificate`)
  33. for k, v := range app.Parameters {
  34. if v == "" {
  35. continue
  36. }
  37. if matches := re.FindStringSubmatch(k); len(matches) > 0 {
  38. port, err := strconv.Atoi(matches[2])
  39. if err != nil {
  40. return nil, err
  41. }
  42. secure := app.Parameters[fmt.Sprintf("%sPort%sSecure", matches[1], matches[2])] == "Yes"
  43. switch prefix := v[8:11]; prefix {
  44. case "acm":
  45. res, err := ACM().DescribeCertificate(&acm.DescribeCertificateInput{
  46. CertificateArn: aws.String(v),
  47. })
  48. if err != nil {
  49. return nil, err
  50. }
  51. parts := strings.Split(v, "-")
  52. id := fmt.Sprintf("acm-%s", parts[len(parts)-1])
  53. ssls = append(ssls, SSL{
  54. Certificate: id,
  55. Domain: *res.Certificate.DomainName,
  56. Expiration: *res.Certificate.NotAfter,
  57. Port: port,
  58. Process: DashName(matches[1]),
  59. Secure: secure,
  60. })
  61. case "iam":
  62. res, err := IAM().GetServerCertificate(&iam.GetServerCertificateInput{
  63. ServerCertificateName: aws.String(certName(app.StackName(), matches[1], port)),
  64. })
  65. if err != nil {
  66. return nil, err
  67. }
  68. pemBlock, _ := pem.Decode([]byte(*res.ServerCertificate.CertificateBody))
  69. c, err := x509.ParseCertificate(pemBlock.Bytes)
  70. if err != nil {
  71. return nil, err
  72. }
  73. ssls = append(ssls, SSL{
  74. Certificate: *res.ServerCertificate.ServerCertificateMetadata.ServerCertificateName,
  75. Domain: c.Subject.CommonName,
  76. Expiration: *res.ServerCertificate.ServerCertificateMetadata.Expiration,
  77. Port: port,
  78. Process: DashName(matches[1]),
  79. Secure: secure,
  80. })
  81. default:
  82. return nil, fmt.Errorf("unknown arn prefix: %s", prefix)
  83. }
  84. }
  85. }
  86. return ssls, nil
  87. }
  88. func UpdateSSL(app, process string, port int, id string) (*SSL, error) {
  89. a, err := GetApp(app)
  90. if err != nil {
  91. return nil, err
  92. }
  93. // validate app is not currently updating
  94. if a.Status != "running" {
  95. return nil, fmt.Errorf("can not update app with status: %s", a.Status)
  96. }
  97. outputs := a.Outputs
  98. balancer := outputs[fmt.Sprintf("%sPort%dBalancerName", UpperName(process), port)]
  99. if balancer == "" {
  100. return nil, fmt.Errorf("Process and port combination unknown")
  101. }
  102. arn := ""
  103. if strings.HasPrefix(id, "acm-") {
  104. uuid := id[4:]
  105. res, err := ACM().ListCertificates(nil)
  106. if err != nil {
  107. return nil, err
  108. }
  109. for _, cert := range res.CertificateSummaryList {
  110. parts := strings.Split(*cert.CertificateArn, "-")
  111. if parts[len(parts)-1] == uuid {
  112. res, err := ACM().DescribeCertificate(&acm.DescribeCertificateInput{
  113. CertificateArn: cert.CertificateArn,
  114. })
  115. if err != nil {
  116. return nil, err
  117. }
  118. if *res.Certificate.Status == "PENDING_VALIDATION" {
  119. return nil, fmt.Errorf("%s is still pending validation", id)
  120. }
  121. arn = *cert.CertificateArn
  122. break
  123. }
  124. }
  125. } else {
  126. res, err := IAM().GetServerCertificate(&iam.GetServerCertificateInput{
  127. ServerCertificateName: aws.String(id),
  128. })
  129. if err != nil {
  130. return nil, err
  131. }
  132. arn = *res.ServerCertificate.ServerCertificateMetadata.Arn
  133. }
  134. // update cloudformation
  135. req := &cloudformation.UpdateStackInput{
  136. StackName: aws.String(a.StackName()),
  137. Capabilities: []*string{aws.String("CAPABILITY_IAM")},
  138. UsePreviousTemplate: aws.Bool(true),
  139. }
  140. params := a.Parameters
  141. params[fmt.Sprintf("%sPort%dCertificate", UpperName(process), port)] = arn
  142. for key, val := range params {
  143. req.Parameters = append(req.Parameters, &cloudformation.Parameter{
  144. ParameterKey: aws.String(key),
  145. ParameterValue: aws.String(val),
  146. })
  147. }
  148. // TODO: The existing cert will be orphaned. Deleting it now could cause
  149. // CF problems if the stack tries to rollback and use the old cert.
  150. _, err = UpdateStack(req)
  151. if err != nil {
  152. return nil, err
  153. }
  154. ssl := SSL{
  155. Port: port,
  156. Process: process,
  157. }
  158. return &ssl, nil
  159. }
  160. // fetch certificate from CF params and parse name from arn
  161. func certName(app, process string, port int) string {
  162. key := fmt.Sprintf("%sPort%dCertificate", UpperName(process), port)
  163. a, err := GetApp(app)
  164. if err != nil {
  165. fmt.Printf(err.Error())
  166. return ""
  167. }
  168. arn := a.Parameters[key]
  169. slice := strings.Split(arn, "/")
  170. return slice[len(slice)-1]
  171. }