├── .gitignore ├── LICENSE ├── common.go ├── iam.go ├── elb.go ├── alb.go ├── acm.go ├── cloudfront.go ├── cli └── aws-cert-utils │ └── main.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | **.pem 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package certutils 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "regexp" 11 | "strings" 12 | 13 | "github.com/aws/aws-sdk-go/aws" 14 | "github.com/aws/aws-sdk-go/aws/session" 15 | "github.com/tkuchiki/aws-sdk-go-config" 16 | survey "gopkg.in/AlecAivazis/survey.v1" 17 | ) 18 | 19 | const minPrivateKeyBitLength = 1024 20 | const maxPrivateKeyBitLength = 2048 21 | 22 | type Tag struct { 23 | Key string 24 | Value string 25 | } 26 | 27 | type CertificateManager struct { 28 | Cert []byte 29 | Chain []byte 30 | Pkey []byte 31 | } 32 | 33 | func NewCertificateManager() *CertificateManager { 34 | return &CertificateManager{} 35 | } 36 | 37 | func (cm *CertificateManager) LoadCertificate(cert, certPath string) error { 38 | var err error 39 | cm.Cert, err = GetCertificateData(cert, certPath) 40 | return err 41 | } 42 | 43 | func (cm *CertificateManager) LoadPrivateKey(pkey, pkeyPath string) error { 44 | var err error 45 | cm.Pkey, err = GetCertificateData(pkey, pkeyPath) 46 | return err 47 | } 48 | 49 | func (cm *CertificateManager) LoadChain(chain, chainPath string) error { 50 | var err error 51 | cm.Chain, err = GetCertificateData(chain, chainPath) 52 | return err 53 | } 54 | 55 | func (cm *CertificateManager) CheckPrivateKeyBitLen() error { 56 | bit, err := PrivateKeyBitLen(cm.Cert, cm.Pkey) 57 | if err != nil { 58 | return err 59 | } 60 | return CheckPrivateKeyBitLen(bit) 61 | } 62 | 63 | func NewAWSSession(accessKey, secretKey, arn, token, region, profile, config, creds string) (*session.Session, error) { 64 | conf := awsconfig.Option{ 65 | Arn: arn, 66 | AccessKey: accessKey, 67 | SecretKey: secretKey, 68 | Region: region, 69 | Token: token, 70 | Profile: profile, 71 | Config: config, 72 | Credentials: creds, 73 | } 74 | 75 | return awsconfig.NewSession(conf) 76 | } 77 | 78 | func readFile(fpath string) ([]byte, error) { 79 | f, err := os.Open(fpath) 80 | if err != nil { 81 | return []byte{}, err 82 | } 83 | 84 | return ioutil.ReadAll(f) 85 | } 86 | 87 | func GetCertificateData(data, fpath string) ([]byte, error) { 88 | if fpath != "" { 89 | return readFile(fpath) 90 | } 91 | 92 | return []byte(data), nil 93 | } 94 | 95 | func PrivateKeyBitLen(certBlock, keyBlock []byte) (int, error) { 96 | cert, err := tls.X509KeyPair(certBlock, keyBlock) 97 | if err != nil { 98 | return 0, err 99 | } 100 | 101 | var bit int 102 | switch privateKey := cert.PrivateKey.(type) { 103 | case *rsa.PrivateKey: 104 | bit = privateKey.N.BitLen() 105 | case *ecdsa.PrivateKey: 106 | bit = privateKey.Curve.Params().BitSize 107 | default: 108 | return 0, fmt.Errorf("unsupported private key") 109 | } 110 | 111 | return bit, nil 112 | } 113 | 114 | func CheckPrivateKeyBitLen(bit int) error { 115 | if bit > maxPrivateKeyBitLength { 116 | return fmt.Errorf("Invalid private key length (%d bit). AWS supports %d and %d bit RSA private key", bit, minPrivateKeyBitLength, maxPrivateKeyBitLength) 117 | } 118 | 119 | return nil 120 | } 121 | 122 | func SplitStatuses(s string) []string { 123 | if strings.ToUpper(s) == "ALL" { 124 | return []string{ 125 | "PENDING_VALIDATION", 126 | "ISSUED", 127 | "INACTIVE", 128 | "EXPIRED", 129 | "VALIDATION_TIMED_OUT", 130 | "REVOKED", 131 | "FAILED", 132 | } 133 | } 134 | 135 | splited := strings.Split(s, ",") 136 | 137 | statuses := make([]string, 0, len(splited)) 138 | for _, val := range splited { 139 | statuses = append(statuses, strings.ToUpper(strings.TrimSpace(val))) 140 | } 141 | 142 | return statuses 143 | } 144 | 145 | func CheckTagValuePattern(val string) error { 146 | if val == "" { 147 | return nil 148 | } 149 | 150 | pattern := `[\p{L}\p{Z}\p{N}_.:\/=+\-@]*` 151 | re := regexp.MustCompile(pattern) 152 | 153 | group := re.FindStringSubmatch(val) 154 | if len(group) > 0 && group[0] == val { 155 | return nil 156 | } 157 | 158 | return fmt.Errorf("Invalid tag value. Tag value supports %s", pattern) 159 | } 160 | 161 | func Choice(choices []string, msg string, pagesize int) string { 162 | val := "" 163 | prompt := &survey.Select{ 164 | Message: msg, 165 | Options: choices, 166 | PageSize: pagesize, 167 | } 168 | survey.AskOne(prompt, &val, nil) 169 | 170 | return val 171 | } 172 | 173 | func toFlatten(strs []*string) string { 174 | return strings.Join(aws.StringValueSlice(strs), " ") 175 | } 176 | 177 | func dryRunMsg() []string { 178 | return []string{ 179 | "# Dry run mode", 180 | "", 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /iam.go: -------------------------------------------------------------------------------- 1 | package certutils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/iam" 10 | "github.com/olekukonko/tablewriter" 11 | ) 12 | 13 | type IAM struct { 14 | client *iam.IAM 15 | } 16 | 17 | type IAMDescription struct { 18 | name string 19 | id string 20 | path string 21 | arn string 22 | } 23 | 24 | func NewIAM(sess *session.Session) *IAM { 25 | return &IAM{ 26 | client: iam.New(sess), 27 | } 28 | } 29 | 30 | func createIAMUploadServerCertificateInput(body, chain, pkey, path, name string) *iam.UploadServerCertificateInput { 31 | input := &iam.UploadServerCertificateInput{} 32 | 33 | input.SetCertificateBody(body) 34 | input.SetCertificateChain(chain) 35 | input.SetPath(path) 36 | input.SetPrivateKey(pkey) 37 | input.SetServerCertificateName(name) 38 | 39 | return input 40 | } 41 | 42 | func (i *IAM) Upload(cert, chain, pkey []byte, path, name string) (string, error) { 43 | out, err := i.client.UploadServerCertificate(createIAMUploadServerCertificateInput(string(cert), string(chain), string(pkey), path, name)) 44 | 45 | return fmt.Sprintf("Uploaded %s %s", name, *out.ServerCertificateMetadata.Arn), err 46 | } 47 | 48 | func createIAMListServerCertificatesInput(marker string, maxItems int64, path string) *iam.ListServerCertificatesInput { 49 | input := &iam.ListServerCertificatesInput{} 50 | 51 | if marker != "" { 52 | input.SetMarker(marker) 53 | } 54 | 55 | if maxItems > 0 { 56 | input.SetMaxItems(maxItems) 57 | } 58 | 59 | if path != "" { 60 | input.SetPathPrefix(path) 61 | } 62 | 63 | return input 64 | } 65 | 66 | func (i *IAM) List(marker string, maxItems int64, path string) ([]IAMDescription, error) { 67 | out, err := i.client.ListServerCertificates(createIAMListServerCertificatesInput(marker, maxItems, path)) 68 | if err != nil { 69 | return []IAMDescription{}, err 70 | } 71 | 72 | descs := make([]IAMDescription, 0, len(out.ServerCertificateMetadataList)) 73 | for _, metadata := range out.ServerCertificateMetadataList { 74 | desc := IAMDescription{ 75 | name: *metadata.ServerCertificateName, 76 | id: *metadata.ServerCertificateId, 77 | path: *metadata.Path, 78 | arn: *metadata.Arn, 79 | } 80 | descs = append(descs, desc) 81 | } 82 | 83 | return descs, err 84 | } 85 | 86 | func (i *IAM) ListMap(marker string, maxItems int64, path string) (map[string]IAMDescription, error) { 87 | out, err := i.client.ListServerCertificates(createIAMListServerCertificatesInput(marker, maxItems, path)) 88 | if err != nil { 89 | return map[string]IAMDescription{}, err 90 | } 91 | 92 | certs := make(map[string]IAMDescription, 0) 93 | for _, metadata := range out.ServerCertificateMetadataList { 94 | certs[*metadata.ServerCertificateId] = IAMDescription{ 95 | name: *metadata.ServerCertificateName, 96 | id: *metadata.ServerCertificateId, 97 | path: *metadata.Path, 98 | arn: *metadata.Arn, 99 | } 100 | } 101 | 102 | return certs, err 103 | } 104 | 105 | func (i *IAM) ListNames(marker string, maxItems int64, path string) ([]string, error) { 106 | descs, err := i.List(marker, maxItems, path) 107 | if err != nil { 108 | return []string{}, err 109 | } 110 | 111 | names := make([]string, 0, len(descs)) 112 | for _, desc := range descs { 113 | names = append(names, desc.name) 114 | } 115 | 116 | return names, err 117 | } 118 | 119 | func createIAMUpdateServerCertificateInput(newPath, newName, name string) *iam.UpdateServerCertificateInput { 120 | input := &iam.UpdateServerCertificateInput{} 121 | 122 | input.SetNewPath(newPath) 123 | input.SetNewServerCertificateName(newName) 124 | input.SetServerCertificateName(name) 125 | 126 | return input 127 | } 128 | 129 | func (i *IAM) Update(newPath, newName, name string) (string, error) { 130 | _, err := i.client.UpdateServerCertificate(createIAMUpdateServerCertificateInput(newPath, newName, name)) 131 | 132 | return fmt.Sprintf("Updated %s -> %s", name, newName), err 133 | } 134 | 135 | func (i *IAM) Delete(name string) (string, error) { 136 | input := &iam.DeleteServerCertificateInput{ServerCertificateName: aws.String(name)} 137 | _, err := i.client.DeleteServerCertificate(input) 138 | 139 | return fmt.Sprintf("Deleted %s", name), err 140 | } 141 | 142 | func (i *IAM) ReadableList(descs []IAMDescription) { 143 | table := tablewriter.NewWriter(os.Stdout) 144 | 145 | table.SetHeader([]string{"Name", "ID", "Path", "Arn"}) 146 | 147 | for _, desc := range descs { 148 | table.Append([]string{desc.name, desc.id, desc.path, desc.arn}) 149 | } 150 | 151 | table.Render() 152 | } 153 | -------------------------------------------------------------------------------- /elb.go: -------------------------------------------------------------------------------- 1 | package certutils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/elb" 10 | "github.com/olekukonko/tablewriter" 11 | ) 12 | 13 | type ELB struct { 14 | client *elb.ELB 15 | } 16 | 17 | type ELBDescription struct { 18 | name string 19 | dnsname string 20 | certs []ELBCertificate 21 | } 22 | 23 | type ELBCertificate struct { 24 | arn string 25 | port int64 26 | } 27 | 28 | func NewELB(sess *session.Session) *ELB { 29 | return &ELB{ 30 | client: elb.New(sess), 31 | } 32 | } 33 | 34 | func (e *ELB) getDescriptions(marker string, certFilter string) ([]ELBDescription, error) { 35 | input := &elb.DescribeLoadBalancersInput{} 36 | out, err := e.client.DescribeLoadBalancers(input) 37 | descs := make([]ELBDescription, 0, len(out.LoadBalancerDescriptions)) 38 | for _, desc := range out.LoadBalancerDescriptions { 39 | elbdesc := ELBDescription{} 40 | 41 | elbdesc.dnsname = *desc.DNSName 42 | elbdesc.name = *desc.LoadBalancerName 43 | 44 | for _, ld := range desc.ListenerDescriptions { 45 | l := ld.Listener 46 | certArn := aws.StringValue(l.SSLCertificateId) 47 | if certArn != "" { 48 | if certFilter != "" && certArn != certFilter { 49 | continue 50 | } 51 | 52 | elbcert := ELBCertificate{ 53 | arn: certArn, 54 | port: *l.LoadBalancerPort, 55 | } 56 | elbdesc.certs = append(elbdesc.certs, elbcert) 57 | } 58 | } 59 | 60 | if len(elbdesc.certs) < 1 { 61 | continue 62 | } 63 | 64 | descs = append(descs, elbdesc) 65 | } 66 | return descs, err 67 | } 68 | 69 | func (e *ELB) List(certFilter string) ([]ELBDescription, error) { 70 | return e.getDescriptions("", certFilter) 71 | } 72 | 73 | func createELBSetLoadBalancerListenerSSLCertificateInput(name string, port int64, certArn string) *elb.SetLoadBalancerListenerSSLCertificateInput { 74 | input := &elb.SetLoadBalancerListenerSSLCertificateInput{} 75 | 76 | input.SetLoadBalancerName(name) 77 | input.SetLoadBalancerPort(port) 78 | input.SetSSLCertificateId(certArn) 79 | 80 | return input 81 | } 82 | 83 | func (e *ELB) getLB(name string) (*elb.DescribeLoadBalancersOutput, error) { 84 | input := &elb.DescribeLoadBalancersInput{} 85 | 86 | names := []string{name} 87 | input.SetLoadBalancerNames(aws.StringSlice(names)) 88 | return e.client.DescribeLoadBalancers(input) 89 | } 90 | 91 | func getListenerCertificateByPort(out *elb.DescribeLoadBalancersOutput, port int64) (string, error) { 92 | for _, desc := range out.LoadBalancerDescriptions { 93 | for _, ld := range desc.ListenerDescriptions { 94 | l := ld.Listener 95 | if *l.LoadBalancerPort == port { 96 | return *l.SSLCertificateId, nil 97 | } 98 | } 99 | } 100 | 101 | return "", fmt.Errorf("Listener not found") 102 | } 103 | 104 | func (e *ELB) Update(name string, port int64, certArn string) (string, error) { 105 | lb, err := e.getLB(name) 106 | if err != nil { 107 | return "", err 108 | } 109 | 110 | destCert, err := getListenerCertificateByPort(lb, port) 111 | if err != nil { 112 | return "", err 113 | } 114 | 115 | _, err = e.client.SetLoadBalancerListenerSSLCertificate(createELBSetLoadBalancerListenerSSLCertificateInput(name, port, certArn)) 116 | 117 | return elbUpdateMsg(name, port, destCert, certArn), err 118 | } 119 | 120 | func elbUpdateMsg(name string, port int64, src, dest string) string { 121 | return fmt.Sprintf("Updated %s:%d %s -> %s", name, port, src, dest) 122 | } 123 | 124 | func (e *ELB) BulkUpdate(srcCertArn, destCertArn string, dryRun bool) ([]string, error) { 125 | descs, err := e.getDescriptions("", srcCertArn) 126 | if err != nil { 127 | return []string{}, err 128 | } 129 | 130 | updates := make([]string, 0) 131 | if dryRun { 132 | updates = append(updates, dryRunMsg()...) 133 | } 134 | 135 | for _, desc := range descs { 136 | for _, cert := range desc.certs { 137 | if dryRun { 138 | updates = append(updates, elbUpdateMsg(desc.name, cert.port, srcCertArn, destCertArn)) 139 | } else { 140 | _, err := e.client.SetLoadBalancerListenerSSLCertificate(createELBSetLoadBalancerListenerSSLCertificateInput(desc.name, cert.port, destCertArn)) 141 | 142 | if err != nil { 143 | return []string{}, err 144 | } 145 | updates = append(updates, elbUpdateMsg(desc.name, cert.port, srcCertArn, destCertArn)) 146 | } 147 | } 148 | } 149 | 150 | return updates, nil 151 | } 152 | 153 | func (e *ELB) ReadableList(descs []ELBDescription) { 154 | table := tablewriter.NewWriter(os.Stdout) 155 | 156 | table.SetHeader([]string{"Name", "Port", "Listener SSL Certificate"}) 157 | table.SetAutoMergeCells(true) 158 | table.SetRowLine(true) 159 | 160 | for _, desc := range descs { 161 | for _, cert := range desc.certs { 162 | table.Append([]string{desc.name, fmt.Sprint(cert.port), cert.arn}) 163 | } 164 | } 165 | 166 | table.Render() 167 | } 168 | -------------------------------------------------------------------------------- /alb.go: -------------------------------------------------------------------------------- 1 | package certutils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/elbv2" 10 | "github.com/olekukonko/tablewriter" 11 | ) 12 | 13 | type ALB struct { 14 | client *elbv2.ELBV2 15 | } 16 | 17 | type ALBDescription struct { 18 | name string 19 | dnsname string 20 | certs []ALBCertificate 21 | } 22 | 23 | type ALBCertificate struct { 24 | arn string 25 | port int64 26 | listenerArn string 27 | } 28 | 29 | func NewALB(sess *session.Session) *ALB { 30 | return &ALB{ 31 | client: elbv2.New(sess), 32 | } 33 | } 34 | 35 | func createALBDescribeListenersInput(lbArn string) *elbv2.DescribeListenersInput { 36 | input := &elbv2.DescribeListenersInput{} 37 | 38 | input.SetLoadBalancerArn(lbArn) 39 | 40 | return input 41 | } 42 | 43 | func (alb *ALB) getLBs(certFilter string) ([]ALBDescription, error) { 44 | input := &elbv2.DescribeLoadBalancersInput{} 45 | out, err := alb.client.DescribeLoadBalancers(input) 46 | lbs := make([]ALBDescription, 0, len(out.LoadBalancers)) 47 | for _, lb := range out.LoadBalancers { 48 | albdesc := ALBDescription{} 49 | 50 | albdesc.dnsname = *lb.DNSName 51 | albdesc.name = *lb.LoadBalancerName 52 | 53 | lout, err := alb.client.DescribeListeners(createALBDescribeListenersInput(*lb.LoadBalancerArn)) 54 | if err != nil { 55 | return []ALBDescription{}, err 56 | } 57 | 58 | for _, l := range lout.Listeners { 59 | for _, cert := range l.Certificates { 60 | if certFilter != "" && certFilter != *cert.CertificateArn { 61 | continue 62 | } 63 | 64 | albcert := ALBCertificate{ 65 | arn: *cert.CertificateArn, 66 | port: *l.Port, 67 | listenerArn: *l.ListenerArn, 68 | } 69 | albdesc.certs = append(albdesc.certs, albcert) 70 | } 71 | } 72 | 73 | if len(albdesc.certs) < 1 { 74 | continue 75 | } 76 | 77 | lbs = append(lbs, albdesc) 78 | } 79 | return lbs, err 80 | } 81 | 82 | func (alb *ALB) List(certFilter string) ([]ALBDescription, error) { 83 | return alb.getLBs(certFilter) 84 | } 85 | 86 | func (alb *ALB) getListener(name string) (*elbv2.Listener, error) { 87 | lbinput := &elbv2.DescribeLoadBalancersInput{} 88 | names := []string{name} 89 | lbinput.SetNames(aws.StringSlice(names)) 90 | lbout, err := alb.client.DescribeLoadBalancers(lbinput) 91 | if err != nil { 92 | return &elbv2.Listener{}, err 93 | } 94 | 95 | if len(lbout.LoadBalancers) < 1 { 96 | return &elbv2.Listener{}, fmt.Errorf("Listener not found") 97 | } 98 | 99 | linput := &elbv2.DescribeListenersInput{} 100 | linput.SetLoadBalancerArn(*lbout.LoadBalancers[0].LoadBalancerArn) 101 | 102 | lout, err := alb.client.DescribeListeners(linput) 103 | if err != nil { 104 | return &elbv2.Listener{}, err 105 | } 106 | 107 | if len(lout.Listeners) < 1 { 108 | return &elbv2.Listener{}, fmt.Errorf("Listener not found") 109 | } 110 | 111 | return lout.Listeners[0], nil 112 | } 113 | 114 | func createALBModifyListenerInput(listenerArn, certArn string) *elbv2.ModifyListenerInput { 115 | input := &elbv2.ModifyListenerInput{} 116 | 117 | cert := &elbv2.Certificate{} 118 | cert.SetCertificateArn(certArn) 119 | 120 | certs := []*elbv2.Certificate{cert} 121 | 122 | input.SetCertificates(certs) 123 | 124 | input.SetListenerArn(listenerArn) 125 | 126 | return input 127 | } 128 | 129 | func (alb *ALB) Update(name string, certArn string) error { 130 | l, err := alb.getListener(name) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | _, err = alb.client.ModifyListener(createALBModifyListenerInput(*l.ListenerArn, certArn)) 136 | 137 | return err 138 | } 139 | 140 | func albUpdateMsg(name string, port int64, src, dest string) string { 141 | return fmt.Sprintf("Updated %s:%d %s -> %s", name, port, src, dest) 142 | } 143 | 144 | func (alb *ALB) BulkUpdate(srcCertArn, destCertArn string, dryRun bool) ([]string, error) { 145 | lbs, err := alb.getLBs(srcCertArn) 146 | if err != nil { 147 | return []string{}, err 148 | } 149 | 150 | updates := make([]string, 0) 151 | if dryRun { 152 | updates = append(updates, dryRunMsg()...) 153 | } 154 | 155 | for _, lb := range lbs { 156 | for _, cert := range lb.certs { 157 | if dryRun { 158 | updates = append(updates, albUpdateMsg(lb.name, cert.port, srcCertArn, destCertArn)) 159 | } else { 160 | _, err := alb.client.ModifyListener(createALBModifyListenerInput(cert.listenerArn, destCertArn)) 161 | 162 | if err != nil { 163 | return []string{}, err 164 | } 165 | updates = append(updates, albUpdateMsg(lb.name, cert.port, srcCertArn, destCertArn)) 166 | } 167 | } 168 | } 169 | 170 | return updates, nil 171 | } 172 | 173 | func (alb *ALB) ReadableList(descs []ALBDescription) { 174 | table := tablewriter.NewWriter(os.Stdout) 175 | 176 | table.SetHeader([]string{"Name", "Port", "Listener SSL Certificate"}) 177 | table.SetAutoMergeCells(true) 178 | table.SetRowLine(true) 179 | 180 | for _, desc := range descs { 181 | for _, cert := range desc.certs { 182 | table.Append([]string{desc.name, fmt.Sprint(cert.port), cert.arn}) 183 | } 184 | } 185 | 186 | table.Render() 187 | } 188 | -------------------------------------------------------------------------------- /acm.go: -------------------------------------------------------------------------------- 1 | package certutils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "time" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/aws/aws-sdk-go/service/acm" 12 | "github.com/olekukonko/tablewriter" 13 | ) 14 | 15 | type ACM struct { 16 | client *acm.ACM 17 | } 18 | 19 | type ACMDescription struct { 20 | arn string 21 | nameTag string 22 | status string 23 | inUseBy []string 24 | notAfter time.Time 25 | domainName string 26 | subjectAlternativeNames []string 27 | } 28 | 29 | func NewACM(sess *session.Session) *ACM { 30 | return &ACM{ 31 | client: acm.New(sess), 32 | } 33 | } 34 | 35 | func createACMImportCertificateInput(cert, chain, pkey []byte) *acm.ImportCertificateInput { 36 | return &acm.ImportCertificateInput{ 37 | Certificate: cert, 38 | CertificateChain: chain, 39 | PrivateKey: pkey, 40 | } 41 | } 42 | 43 | func (a *ACM) Import(cert, chain, pkey []byte) (string, string, error) { 44 | out, err := a.client.ImportCertificate(createACMImportCertificateInput(cert, chain, pkey)) 45 | 46 | return *out.CertificateArn, fmt.Sprintf("Imported %s", *out.CertificateArn), err 47 | } 48 | 49 | func createACMListCertificatesInput(statuses []string, maxItems int64, nextToken string) *acm.ListCertificatesInput { 50 | linput := &acm.ListCertificatesInput{} 51 | 52 | if len(statuses) > 0 { 53 | linput.SetCertificateStatuses(aws.StringSlice(statuses)) 54 | } 55 | 56 | if maxItems > 0 { 57 | linput.SetMaxItems(maxItems) 58 | } 59 | 60 | if nextToken != "" { 61 | linput.SetNextToken(nextToken) 62 | } 63 | 64 | return linput 65 | } 66 | 67 | func (a *ACM) listTags(arn string) (*acm.ListTagsForCertificateOutput, error) { 68 | input := &acm.ListTagsForCertificateInput{} 69 | input.SetCertificateArn(arn) 70 | 71 | return a.client.ListTagsForCertificate(input) 72 | } 73 | 74 | func (a *ACM) getNameTag(arn string) (string, error) { 75 | out, err := a.listTags(arn) 76 | if err != nil { 77 | return "", err 78 | } 79 | 80 | for _, tag := range out.Tags { 81 | if strings.ToLower(aws.StringValue(tag.Key)) == "name" { 82 | return *tag.Value, nil 83 | } 84 | } 85 | 86 | return "", fmt.Errorf("Name tag not found") 87 | } 88 | 89 | func (a *ACM) List(statuses string, maxItems int64, nextToken string) ([]ACMDescription, error) { 90 | cout, err := a.client.ListCertificates(createACMListCertificatesInput(SplitStatuses(statuses), maxItems, nextToken)) 91 | 92 | descs := make([]ACMDescription, 0, len(cout.CertificateSummaryList)) 93 | for _, summary := range cout.CertificateSummaryList { 94 | dcout, err := a.client.DescribeCertificate(&acm.DescribeCertificateInput{ 95 | CertificateArn: summary.CertificateArn, 96 | }) 97 | if err != nil { 98 | return []ACMDescription{}, err 99 | } 100 | 101 | cert := dcout.Certificate 102 | nameTag, _ := a.getNameTag(*cert.CertificateArn) 103 | 104 | desc := ACMDescription{ 105 | arn: *cert.CertificateArn, 106 | nameTag: nameTag, 107 | status: *cert.Status, 108 | inUseBy: aws.StringValueSlice(cert.InUseBy), 109 | notAfter: aws.TimeValue(cert.NotAfter), 110 | domainName: *cert.DomainName, 111 | subjectAlternativeNames: aws.StringValueSlice(cert.SubjectAlternativeNames), 112 | } 113 | 114 | descs = append(descs, desc) 115 | } 116 | 117 | return descs, err 118 | } 119 | 120 | func (a *ACM) ListDeleteTargets(statuses string, maxItems int64, nextToken string) ([]string, map[string]string, error) { 121 | descs, err := a.List(statuses, maxItems, nextToken) 122 | if err != nil { 123 | return []string{}, map[string]string{}, err 124 | } 125 | 126 | targets := make(map[string]string, 0) 127 | arns := make([]string, 0, len(descs)) 128 | for _, desc := range descs { 129 | tagArn := fmt.Sprintf("[%s] %s", desc.nameTag, desc.arn) 130 | arns = append(arns, tagArn) 131 | targets[tagArn] = desc.arn 132 | } 133 | 134 | return arns, targets, err 135 | } 136 | 137 | func toACMTags(tags []Tag) []*acm.Tag { 138 | if len(tags) <= 0 { 139 | return []*acm.Tag{} 140 | } 141 | 142 | acmTags := make([]*acm.Tag, 0, len(tags)) 143 | 144 | for _, t := range tags { 145 | acmTag := &acm.Tag{ 146 | Key: aws.String(t.Key), 147 | Value: aws.String(t.Value), 148 | } 149 | 150 | acmTags = append(acmTags, acmTag) 151 | } 152 | 153 | return acmTags 154 | } 155 | 156 | func createACMAddTagsToCertificateInput(arn string, tags []Tag) *acm.AddTagsToCertificateInput { 157 | tinput := &acm.AddTagsToCertificateInput{} 158 | tinput.SetCertificateArn(arn) 159 | tinput.SetTags(toACMTags(tags)) 160 | 161 | return tinput 162 | } 163 | 164 | func (a *ACM) AddTags(arn string, tags []Tag) error { 165 | _, err := a.client.AddTagsToCertificate(createACMAddTagsToCertificateInput(arn, tags)) 166 | 167 | return err 168 | } 169 | 170 | func createACMDeleteCertificateInput(arn string) *acm.DeleteCertificateInput { 171 | dinput := &acm.DeleteCertificateInput{} 172 | 173 | dinput.SetCertificateArn(arn) 174 | 175 | return dinput 176 | } 177 | 178 | func (a *ACM) Delete(arn string) (string, error) { 179 | _, err := a.client.DeleteCertificate(createACMDeleteCertificateInput(arn)) 180 | 181 | return fmt.Sprintf("Deleted %s", arn), err 182 | } 183 | 184 | func (a *ACM) ReadableList(descs []ACMDescription) { 185 | table := tablewriter.NewWriter(os.Stdout) 186 | 187 | table.SetHeader([]string{"Name tag", "Domain Name", "Additional Name", "In Use?", "Not After", "Certificate Arn"}) 188 | table.SetAutoMergeCells(true) 189 | table.SetRowLine(true) 190 | 191 | for _, desc := range descs { 192 | inUse := "No" 193 | if len(desc.inUseBy) > 0 { 194 | inUse = "Yes" 195 | } 196 | for _, name := range desc.subjectAlternativeNames { 197 | if name == desc.domainName { 198 | continue 199 | } 200 | table.Append([]string{desc.nameTag, desc.domainName, name, inUse, desc.notAfter.String(), desc.arn}) 201 | } 202 | } 203 | 204 | table.Render() 205 | } 206 | -------------------------------------------------------------------------------- /cloudfront.go: -------------------------------------------------------------------------------- 1 | package certutils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/aws/aws-sdk-go/aws/session" 10 | "github.com/aws/aws-sdk-go/service/cloudfront" 11 | "github.com/aws/aws-sdk-go/service/iam" 12 | "github.com/olekukonko/tablewriter" 13 | ) 14 | 15 | type CloudFront struct { 16 | client *cloudfront.CloudFront 17 | iamClient *IAM 18 | marker string 19 | maxItems int64 20 | } 21 | 22 | type CFDistribution struct { 23 | id string 24 | domain string 25 | cert string 26 | aliasesStr string 27 | aliases []string 28 | } 29 | 30 | func NewCloudFront(sess *session.Session, marker string, maxItems int64) *CloudFront { 31 | return &CloudFront{ 32 | client: cloudfront.New(sess), 33 | iamClient: &IAM{ 34 | client: iam.New(sess), 35 | }, 36 | marker: marker, 37 | maxItems: int64(maxItems), 38 | } 39 | } 40 | 41 | func createCFListDistributionsInput(marker string, maxItems int64) *cloudfront.ListDistributionsInput { 42 | dinput := &cloudfront.ListDistributionsInput{} 43 | 44 | if marker != "" { 45 | dinput.SetMarker(marker) 46 | } 47 | 48 | if maxItems > 0 { 49 | dinput.SetMaxItems(int64(maxItems)) 50 | } 51 | 52 | return dinput 53 | } 54 | 55 | func (cf *CloudFront) getDistributions(certFilter, aliasesFilter string) ([]CFDistribution, error) { 56 | out, err := cf.client.ListDistributions(createCFListDistributionsInput(cf.marker, cf.maxItems)) 57 | if err != nil { 58 | return []CFDistribution{}, err 59 | } 60 | 61 | iamDescs, err := cf.iamClient.ListMap("", int64(0), "") 62 | if err != nil { 63 | return []CFDistribution{}, err 64 | } 65 | 66 | dists := make([]CFDistribution, 0, len(out.DistributionList.Items)) 67 | for _, summary := range out.DistributionList.Items { 68 | dist := CFDistribution{} 69 | 70 | vCert := summary.ViewerCertificate 71 | var iamCert string 72 | if aws.StringValue(vCert.ACMCertificateArn) != "" { 73 | dist.cert = *vCert.ACMCertificateArn 74 | } else if aws.StringValue(vCert.IAMCertificateId) != "" { 75 | dist.cert = *vCert.IAMCertificateId 76 | iamCert = fmt.Sprintf("%s | %s", *vCert.IAMCertificateId, iamDescs[*vCert.IAMCertificateId].name) 77 | } else { 78 | continue 79 | } 80 | 81 | if certFilter != "" && dist.cert != certFilter { 82 | continue 83 | } 84 | 85 | if iamCert != "" { 86 | dist.cert = iamCert 87 | } 88 | 89 | aliases := summary.Aliases.Items 90 | 91 | if len(aliases) > 0 { 92 | dist.aliases = aws.StringValueSlice(aliases) 93 | dist.aliasesStr = toFlatten(aliases) 94 | } 95 | 96 | if aliasesFilter != "" && (dist.aliasesStr == "" || strings.Index(dist.aliasesStr, aliasesFilter) < 0) { 97 | continue 98 | } 99 | 100 | dist.id = *summary.Id 101 | dist.domain = *summary.DomainName 102 | 103 | dists = append(dists, dist) 104 | } 105 | return dists, err 106 | } 107 | 108 | func (cf *CloudFront) List(certFilter, aliasesFilter string) ([]CFDistribution, error) { 109 | return cf.getDistributions(certFilter, aliasesFilter) 110 | } 111 | 112 | func createCFDistributionConfig(vc *cloudfront.ViewerCertificate) *cloudfront.DistributionConfig { 113 | dc := &cloudfront.DistributionConfig{} 114 | 115 | dc.SetViewerCertificate(vc) 116 | 117 | return dc 118 | } 119 | 120 | func createCFViewerCertificate(vc *cloudfront.ViewerCertificate, service, cert string) *cloudfront.ViewerCertificate { 121 | newvc := &cloudfront.ViewerCertificate{} 122 | 123 | switch service { 124 | case "acm": 125 | newvc.SetACMCertificateArn(cert) 126 | case "iam": 127 | newvc.SetIAMCertificateId(cert) 128 | } 129 | newvc.SetCloudFrontDefaultCertificate(false) 130 | newvc.SetMinimumProtocolVersion(*vc.MinimumProtocolVersion) 131 | newvc.SetSSLSupportMethod(*vc.SSLSupportMethod) 132 | 133 | return newvc 134 | } 135 | 136 | func createCFUpdateDistributionInput(distOut *cloudfront.GetDistributionOutput, service, cert string) *cloudfront.UpdateDistributionInput { 137 | dinput := &cloudfront.UpdateDistributionInput{} 138 | 139 | dist := distOut.Distribution 140 | 141 | dinput.SetId(*dist.Id) 142 | dinput.SetIfMatch(*distOut.ETag) 143 | 144 | dc := dist.DistributionConfig 145 | vc := dc.ViewerCertificate 146 | 147 | newvc := createCFViewerCertificate(vc, service, cert) 148 | 149 | dc.SetViewerCertificate(newvc) 150 | 151 | dinput.SetDistributionConfig(dc) 152 | 153 | return dinput 154 | } 155 | 156 | func createGetDistributionInput(id string) *cloudfront.GetDistributionInput { 157 | dinput := &cloudfront.GetDistributionInput{} 158 | 159 | dinput.SetId(id) 160 | 161 | return dinput 162 | } 163 | 164 | func (cf *CloudFront) GetDistribution(id string) (*cloudfront.GetDistributionOutput, error) { 165 | return cf.client.GetDistribution(createGetDistributionInput(id)) 166 | } 167 | 168 | func getCertificate(vc *cloudfront.ViewerCertificate) string { 169 | if aws.StringValue(vc.ACMCertificateArn) != "" { 170 | return aws.StringValue(vc.ACMCertificateArn) 171 | } 172 | if aws.StringValue(vc.IAMCertificateId) != "" { 173 | return aws.StringValue(vc.IAMCertificateId) 174 | } 175 | 176 | return "" 177 | } 178 | 179 | func (cf *CloudFront) Update(id, service, cert string) (string, error) { 180 | distOut, err := cf.GetDistribution(id) 181 | if err != nil { 182 | return "", err 183 | } 184 | 185 | srcCert := getCertificate(distOut.Distribution.DistributionConfig.ViewerCertificate) 186 | 187 | _, err = cf.client.UpdateDistribution(createCFUpdateDistributionInput(distOut, service, cert)) 188 | if err != nil { 189 | return "", err 190 | } 191 | 192 | aliases := toFlatten(distOut.Distribution.DistributionConfig.Aliases.Items) 193 | 194 | return cfUpdateMsg(id, aliases, srcCert, cert), nil 195 | } 196 | 197 | func cfUpdateMsg(id, aliases, src, dest string) string { 198 | return fmt.Sprintf("Updated %s %s %s -> %s", id, aliases, src, dest) 199 | } 200 | 201 | func (cf *CloudFront) BulkUpdate(service, srcCert, destCert string, dryRun bool) ([]string, error) { 202 | dists, err := cf.getDistributions(srcCert, "") 203 | if err != nil { 204 | return []string{}, err 205 | } 206 | 207 | updates := make([]string, 0) 208 | if dryRun { 209 | updates = append(updates, dryRunMsg()...) 210 | } 211 | 212 | for _, dist := range dists { 213 | if dryRun { 214 | updates = append(updates, cfUpdateMsg(dist.id, dist.aliasesStr, srcCert, destCert)) 215 | } else { 216 | distOut, err := cf.GetDistribution(dist.id) 217 | if err != nil { 218 | return []string{}, err 219 | } 220 | 221 | _, err = cf.client.UpdateDistribution(createCFUpdateDistributionInput(distOut, service, destCert)) 222 | 223 | if err != nil { 224 | return []string{}, err 225 | } 226 | updates = append(updates, cfUpdateMsg(dist.id, dist.aliasesStr, srcCert, destCert)) 227 | } 228 | 229 | } 230 | 231 | return updates, nil 232 | } 233 | 234 | func (cf *CloudFront) ReadableList(dists []CFDistribution) { 235 | table := tablewriter.NewWriter(os.Stdout) 236 | 237 | table.SetHeader([]string{"Distribution ID", "Aliases", "SSL Certificate"}) 238 | table.SetAutoMergeCells(true) 239 | table.SetRowLine(true) 240 | 241 | for _, dist := range dists { 242 | for _, alias := range dist.aliases { 243 | table.Append([]string{dist.id, alias, dist.cert}) 244 | } 245 | } 246 | 247 | table.Render() 248 | } 249 | -------------------------------------------------------------------------------- /cli/aws-cert-utils/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/tkuchiki/aws-cert-utils" 10 | "gopkg.in/alecthomas/kingpin.v2" 11 | ) 12 | 13 | var ( 14 | crtUtils = kingpin.New("aws-cert-utils", "Certificate Utility for AWS(ACM, IAM, ALB, ELB, CloudFront)") 15 | awsAccessKeyID = crtUtils.Flag("access-key", "The AWS access key ID").String() 16 | awsSecretAccessKey = crtUtils.Flag("secret-key", "The AWS secret access key").String() 17 | awsArn = crtUtils.Flag("assume-role-arn", "The AWS assume role ARN").String() 18 | awsToken = crtUtils.Flag("token", "The AWS access token").String() 19 | awsRegion = crtUtils.Flag("region", "The AWS region").String() 20 | awsProfile = crtUtils.Flag("profile", "The AWS CLI profile").String() 21 | awsConfig = crtUtils.Flag("aws-config", "The AWS CLI Config file").String() 22 | awsCreds = crtUtils.Flag("credentials", "The AWS CLI Credential file").String() 23 | 24 | // acm 25 | acmCmd = crtUtils.Command("acm", "AWS Certificate Manager (ACM)") 26 | // acm list 27 | acmListCmd = acmCmd.Command("list", "Retrieves a list of ACM Certificates and the domain name for each") 28 | acmListStatuses = acmListCmd.Flag("cert-statuses", "The status or statuses on which to filter the list of ACM Certificates(comma separated)").Default("ALL").String() 29 | acmListMaxItems = acmListCmd.Flag("max-items", "The total number of items to return in the command's output").Int() 30 | 31 | // acm import 32 | acmImportCmd = acmCmd.Command("import", "Imports an SSL/TLS certificate into AWS Certificate Manager (ACM) to use with ACM's integrated AWS services") 33 | acmImportCertPath = acmImportCmd.Flag("cert-path", "Path to certificate").String() 34 | acmImportChainPath = acmImportCmd.Flag("chain-path", "Path to certificate chain").String() 35 | acmImportPkeyPath = acmImportCmd.Flag("pkey-path", "Path to private key").String() 36 | acmImportCert = acmImportCmd.Flag("cert", "The certificate to import").String() 37 | acmImportChain = acmImportCmd.Flag("chain", "The certificate chain").String() 38 | acmImportPkey = acmImportCmd.Flag("pkey", "The private key that matches the public key in the certificate").String() 39 | acmImportName = acmImportCmd.Flag("name", "The name tag value").String() 40 | 41 | // acm delete 42 | acmDeleteCmd = acmCmd.Command("delete", "Deletes an ACM Certificate and its associated private key") 43 | acmDeleteArn = acmDeleteCmd.Flag("arn", "String that contains the ARN of the ACM Certificate to be deleted").String() 44 | acmDeleteStatuses = acmDeleteCmd.Flag("cert-statuses", "The status or statuses on which to filter the list of ACM Certificates(comma separated)").Default("ALL").String() 45 | acmDeleteMaxItems = acmDeleteCmd.Flag("max-items", "The total number of items to return in the command's output").Int() 46 | 47 | // iam 48 | iamCmd = crtUtils.Command("iam", "AWS Identity and Access Management (IAM)") 49 | // iam list 50 | iamListCmd = iamCmd.Command("list", "Lists the server certificates stored in IAM that have the specified path prefix") 51 | iamListMarker = iamListCmd.Flag("marker", "Paginating results and only after you receive a response indicating that the results are truncated").String() 52 | iamListMaxItems = iamListCmd.Flag("max-items", "The total number of items to return").Int() 53 | iamListPathPrefix = iamListCmd.Flag("path-prefix", "The path prefix for filtering the results").Default("/").String() 54 | 55 | // iam upload 56 | iamUploadCmd = iamCmd.Command("upload", "Uploads a server certificate entity for the AWS account") 57 | iamUploadCertPath = iamUploadCmd.Flag("cert-path", "Path to certificate").String() 58 | iamUploadChainPath = iamUploadCmd.Flag("chain-path", "Path tocertificate chain").String() 59 | iamUploadPkeyPath = iamUploadCmd.Flag("pkey-path", "Path to private key").String() 60 | iamUploadCert = iamUploadCmd.Flag("cert", "The contents of the public key certificate").String() 61 | iamUploadChain = iamUploadCmd.Flag("chain", "The contents of the certificate chain").String() 62 | iamUploadPkey = iamUploadCmd.Flag("pkey", "The contents of the private key").String() 63 | iamUploadPath = iamUploadCmd.Flag("path", "The path for the server certificate").Default("/").String() 64 | iamUploadName = iamUploadCmd.Flag("name", "The name for the server certificate").String() 65 | 66 | // iam update 67 | iamUpdateCmd = iamCmd.Command("update", "Updates the name and/or the path of the specified server certificate stored in IAM") 68 | iamUpdateNewPath = iamUpdateCmd.Flag("new-path", "The new path for the server certificate").String() 69 | iamUpdateNewName = iamUpdateCmd.Flag("new-name", "The new name for the server certificate").String() 70 | iamUpdateName = iamUpdateCmd.Flag("name", "The name for the server certificate").String() 71 | 72 | // iam delete 73 | iamDeleteCmd = iamCmd.Command("delete", "Deletes the specified server certificate") 74 | iamDeleteName = iamDeleteCmd.Flag("name", "The name of the server certificate you want to delete").String() 75 | iamDeleteMarker = iamDeleteCmd.Flag("marker", "Paginating results and only after you receive a response indicating that the results are truncated").String() 76 | iamDeleteMaxItems = iamDeleteCmd.Flag("max-items", "The total number of items to return").Int() 77 | iamDeletePathPrefix = iamDeleteCmd.Flag("path-prefix", "The path prefix for filtering the results").Default("/").String() 78 | 79 | // cloudfront 80 | cfCmd = crtUtils.Command("cloudfront", "Amazon CloudFront") 81 | cfMarker = cfCmd.Flag("marker", "Paginating results and only after you receive a response indicating that the results are truncated").String() 82 | cfMaxItems = cfCmd.Flag("max-items", "The total number of items to return in the command's output").Int() 83 | 84 | // cloudfront list 85 | cfListCmd = cfCmd.Command("list", "Lists the distributions") 86 | cfListCertFilter = cfListCmd.Flag("cert", "ACM Arn or IAM Certificate ID").String() 87 | cfListAliasesFilter = cfListCmd.Flag("aliases", "Domain name").String() 88 | 89 | // cloudfront update 90 | cfUpdateCmd = cfCmd.Command("update", "Updates the configuration for a distribution") 91 | cfUpdateDistId = cfUpdateCmd.Flag("dist-id", "The distribution's id").String() 92 | cfUpdateACMArn = cfUpdateCmd.Flag("acm-arn", "String that contains the ARN of the ACM Certificate").String() 93 | cfUpdateIAMId = cfUpdateCmd.Flag("iam-id", "String that contains the IAM Certificate ID").String() 94 | 95 | // cloudfront bulk-update 96 | cfBUpdateCmd = cfCmd.Command("bulk-update", "Updates the configuration for distributions") 97 | cfBUpdateSrcACMArn = cfBUpdateCmd.Flag("source-acm-arn", "String that contains the ARN of the source ACM Certificate").String() 98 | cfBUpdateSrcIAMId = cfBUpdateCmd.Flag("source-iam-id", "String that contains the source IAM Certificate ID").String() 99 | cfBUpdateDestACMArn = cfBUpdateCmd.Flag("dest-acm-arn", "String that contains the ARN of the destination ACM Certificate").String() 100 | cfBUpdateDestIAMId = cfBUpdateCmd.Flag("dest-iam-id", "String that contains the destination IAM Certificate ID").String() 101 | cfBUpdateNoDryRun = cfBUpdateCmd.Flag("no-dry-run", "Disable dry-run mode").Bool() 102 | 103 | // elb 104 | elbCmd = crtUtils.Command("elb", "Elastic Load Balancing") 105 | // elb list 106 | elbListCmd = elbCmd.Command("list", "Describes the specified the load balancers") 107 | elbListCertFilter = elbListCmd.Flag("cert", "String that contains the ARN of the ACM/IAM Certificate").PlaceHolder("ARN").String() 108 | // elb update 109 | elbUpdateCmd = elbCmd.Command("update", "Updates the specified a listener from the specified load balancer") 110 | elbUpdateName = elbUpdateCmd.Flag("name", "The name of the load balancer").String() 111 | elbUpdatePort = elbUpdateCmd.Flag("port", "The port that uses the specified SSL certificate").Default("443").Int() 112 | elbUpdateArn = elbUpdateCmd.Flag("cert-arn", "The ARN of the ACM/IAM SSL Certificate").String() 113 | 114 | // elb bulk-update 115 | elbBUpdateCmd = elbCmd.Command("bulk-update", "Updates the specified listeners from the specified load balancer") 116 | elbBUpdateSrcCertArn = elbBUpdateCmd.Flag("source-cert-arn", "The ARN of the source ACM/IAM SSL Certificate").String() 117 | elbBUpdateDestCertArn = elbBUpdateCmd.Flag("dest-cert-arn", "The ARN of the destination ACM/IAM SSL Certificate").String() 118 | elbBUpdateNoDryRun = elbBUpdateCmd.Flag("no-dry-run", "Disable dry-run mode").Bool() 119 | 120 | // alb 121 | albCmd = crtUtils.Command("alb", "Application Load Balancing") 122 | // alb list 123 | albListCmd = albCmd.Command("list", "Describes the specified load balancers") 124 | albListCertFilter = albListCmd.Flag("cert", "The ARN of the ACM/IAM SSL Certificate").PlaceHolder("ARN").String() 125 | 126 | // alb update 127 | albUpdateCmd = albCmd.Command("update", "Updates the specified a listener from the specified load balancer") 128 | albUpdateName = albUpdateCmd.Flag("name", "The name of the load balancer").String() 129 | albUpdateArn = albUpdateCmd.Flag("cert-arn", "The ARN of the source ACM/IAM SSL Certificate").String() 130 | 131 | // alb bulk-update 132 | albBUpdateCmd = albCmd.Command("bulk-update", "Updates the specified listeners from the specified load balancer") 133 | albBUpdateSrcCertArn = albBUpdateCmd.Flag("source-cert-arn", "The ARN of the source ACM/IAM SSL Certificate").String() 134 | albBUpdateDestCertArn = albBUpdateCmd.Flag("dest-cert-arn", "The ARN of the destination ACM/IAM SSL Certificate").String() 135 | albBUpdateNoDryRun = albBUpdateCmd.Flag("no-dry-run", "Disable dry-run mode").Bool() 136 | ) 137 | 138 | func main() { 139 | crtUtils.Version("0.1.1") 140 | subCmd, err := crtUtils.Parse(os.Args[1:]) 141 | 142 | if err != nil { 143 | log.Fatal(err) 144 | } 145 | 146 | cmds := strings.Split(subCmd, " ") 147 | 148 | var region string 149 | if cmds[0] == "iam" || cmds[0] == "cloudfront" { 150 | region = "us-east-1" 151 | } else { 152 | region = *awsRegion 153 | } 154 | 155 | sess, err := certutils.NewAWSSession(*awsAccessKeyID, *awsSecretAccessKey, *awsArn, *awsToken, region, *awsProfile, *awsConfig, *awsCreds) 156 | if err != nil { 157 | log.Fatal(err) 158 | } 159 | 160 | switch cmds[0] { 161 | case "acm": 162 | a := certutils.NewACM(sess) 163 | switch cmds[1] { 164 | case "list": 165 | out, err := a.List(*acmListStatuses, int64(*acmListMaxItems), "") 166 | if err != nil { 167 | log.Fatal(err) 168 | } 169 | 170 | a.ReadableList(out) 171 | case "import": 172 | err := certutils.CheckTagValuePattern(*acmImportName) 173 | if err != nil { 174 | log.Fatal(err) 175 | } 176 | 177 | cm := certutils.NewCertificateManager() 178 | 179 | err = cm.LoadCertificate(*acmImportCert, *acmImportCertPath) 180 | if err != nil { 181 | log.Fatal(err) 182 | } 183 | 184 | err = cm.LoadChain(*acmImportChain, *acmImportChainPath) 185 | if err != nil { 186 | log.Fatal(err) 187 | } 188 | 189 | err = cm.LoadPrivateKey(*acmImportPkey, *acmImportPkeyPath) 190 | if err != nil { 191 | log.Fatal(err) 192 | } 193 | 194 | err = cm.CheckPrivateKeyBitLen() 195 | if err != nil { 196 | log.Fatal(err) 197 | } 198 | 199 | arn, msg, err := a.Import(cm.Cert, cm.Chain, cm.Pkey) 200 | if err != nil { 201 | log.Fatal(err) 202 | } 203 | 204 | if *acmImportName != "" { 205 | err = a.AddTags(arn, []certutils.Tag{ 206 | certutils.Tag{ 207 | Key: "Name", 208 | Value: *acmImportName, 209 | }, 210 | }) 211 | if err != nil { 212 | log.Fatal(err) 213 | } 214 | } 215 | 216 | fmt.Println(msg) 217 | case "delete": 218 | arn := *acmDeleteArn 219 | if arn == "" { 220 | arns, targets, err := a.ListDeleteTargets(*acmDeleteStatuses, int64(*acmDeleteMaxItems), "") 221 | if err != nil { 222 | log.Fatal(err) 223 | } 224 | arn = certutils.Choice(arns, "Choose the server certificate you want to delete : ", 20) 225 | 226 | arn = targets[arn] 227 | 228 | if arn == "" { 229 | os.Exit(0) 230 | } 231 | } 232 | 233 | msg, err := a.Delete(arn) 234 | if err != nil { 235 | log.Fatal(err) 236 | } 237 | 238 | fmt.Println(msg) 239 | } 240 | case "iam": 241 | i := certutils.NewIAM(sess) 242 | switch cmds[1] { 243 | case "list": 244 | descs, err := i.List(*iamListMarker, int64(*iamListMaxItems), *iamListPathPrefix) 245 | if err != nil { 246 | log.Fatal(err) 247 | } 248 | 249 | i.ReadableList(descs) 250 | case "upload": 251 | cm := certutils.NewCertificateManager() 252 | 253 | err = cm.LoadCertificate(*iamUploadCert, *iamUploadCertPath) 254 | if err != nil { 255 | log.Fatal(err) 256 | } 257 | 258 | err = cm.LoadChain(*iamUploadChain, *iamUploadChainPath) 259 | if err != nil { 260 | log.Fatal(err) 261 | } 262 | 263 | err = cm.LoadPrivateKey(*iamUploadPkey, *iamUploadPkeyPath) 264 | if err != nil { 265 | log.Fatal(err) 266 | } 267 | 268 | err = cm.CheckPrivateKeyBitLen() 269 | if err != nil { 270 | log.Fatal(err) 271 | } 272 | 273 | msg, err := i.Upload(cm.Cert, cm.Chain, cm.Pkey, *iamUploadPath, *iamUploadName) 274 | if err != nil { 275 | log.Fatal(err) 276 | } 277 | 278 | fmt.Println(msg) 279 | case "update": 280 | msg, err := i.Update(*iamUpdateNewPath, *iamUpdateNewName, *iamUpdateName) 281 | if err != nil { 282 | log.Fatal(err) 283 | } 284 | 285 | fmt.Println(msg) 286 | case "delete": 287 | name := *iamDeleteName 288 | if name == "" { 289 | names, err := i.ListNames(*iamDeleteMarker, int64(*iamDeleteMaxItems), *iamDeletePathPrefix) 290 | if err != nil { 291 | log.Fatal(err) 292 | } 293 | name = certutils.Choice(names, "Choose the server certificate you want to delete : ", 20) 294 | 295 | if name == "" { 296 | os.Exit(0) 297 | } 298 | } 299 | 300 | msg, err := i.Delete(name) 301 | if err != nil { 302 | log.Fatal(err) 303 | } 304 | 305 | fmt.Println(msg) 306 | } 307 | case "cloudfront": 308 | cf := certutils.NewCloudFront(sess, *cfMarker, int64(*cfMaxItems)) 309 | switch cmds[1] { 310 | case "list": 311 | dists, err := cf.List(*cfListCertFilter, *cfListAliasesFilter) 312 | if err != nil { 313 | log.Fatal(err) 314 | } 315 | 316 | cf.ReadableList(dists) 317 | case "update": 318 | var service, cert string 319 | if *cfUpdateACMArn == "" && *cfUpdateIAMId == "" { 320 | log.Fatal("--acm-arn or --iam-id is required.") 321 | } else if *cfUpdateACMArn != "" && *cfUpdateIAMId != "" { 322 | log.Fatal("--acm-arn or --iam-id but not both.") 323 | } else if *cfUpdateACMArn != "" { 324 | cert = *cfUpdateACMArn 325 | service = "acm" 326 | } else { 327 | cert = *cfUpdateIAMId 328 | service = "iam" 329 | } 330 | 331 | dist, err := cf.Update(*cfUpdateDistId, service, cert) 332 | if err != nil { 333 | log.Fatal(err) 334 | } 335 | 336 | fmt.Println(dist) 337 | case "bulk-update": 338 | var service, srcCert, destCert string 339 | if *cfBUpdateSrcACMArn == "" && *cfBUpdateSrcIAMId == "" { 340 | log.Fatal("--source-acm-arn or --source-iam-id is required.") 341 | } else if *cfBUpdateSrcACMArn != "" && *cfBUpdateSrcIAMId != "" { 342 | log.Fatal("--source-acm-arn or --source-iam-id but not both.") 343 | } else if *cfBUpdateSrcACMArn != "" { 344 | srcCert = *cfBUpdateSrcACMArn 345 | } else { 346 | srcCert = *cfBUpdateSrcIAMId 347 | } 348 | 349 | if *cfBUpdateDestACMArn == "" && *cfBUpdateDestIAMId == "" { 350 | log.Fatal("--dest-acm-arn or --dest-iam-id is required.") 351 | } else if *cfBUpdateDestACMArn != "" && *cfBUpdateDestIAMId != "" { 352 | log.Fatal("--dest-acm-arn or --dest-iam-id but not both.") 353 | } else if *cfBUpdateDestACMArn != "" { 354 | destCert = *cfBUpdateDestACMArn 355 | service = "acm" 356 | } else { 357 | destCert = *cfBUpdateDestIAMId 358 | service = "iam" 359 | } 360 | 361 | dists, err := cf.BulkUpdate(service, srcCert, destCert, !*cfBUpdateNoDryRun) 362 | if err != nil { 363 | log.Fatal(err) 364 | } 365 | 366 | for _, dist := range dists { 367 | fmt.Println(dist) 368 | } 369 | } 370 | case "elb": 371 | e := certutils.NewELB(sess) 372 | switch cmds[1] { 373 | case "list": 374 | descs, err := e.List(*elbListCertFilter) 375 | if err != nil { 376 | log.Fatal(err) 377 | } 378 | 379 | e.ReadableList(descs) 380 | case "update": 381 | update, err := e.Update(*elbUpdateName, int64(*elbUpdatePort), *elbUpdateArn) 382 | if err != nil { 383 | log.Fatal(err) 384 | } 385 | 386 | fmt.Println(update) 387 | case "bulk-update": 388 | updates, err := e.BulkUpdate(*elbBUpdateSrcCertArn, *elbBUpdateDestCertArn, !*elbBUpdateNoDryRun) 389 | if err != nil { 390 | log.Fatal(err) 391 | } 392 | 393 | for _, u := range updates { 394 | fmt.Println(u) 395 | } 396 | } 397 | case "alb": 398 | alb := certutils.NewALB(sess) 399 | switch cmds[1] { 400 | case "list": 401 | descs, err := alb.List(*albListCertFilter) 402 | if err != nil { 403 | log.Fatal(err) 404 | } 405 | 406 | alb.ReadableList(descs) 407 | case "update": 408 | err := alb.Update(*albUpdateName, *albUpdateArn) 409 | if err != nil { 410 | log.Fatal(err) 411 | } 412 | case "bulk-update": 413 | albs, err := alb.BulkUpdate(*albBUpdateSrcCertArn, *albBUpdateDestCertArn, !*albBUpdateNoDryRun) 414 | if err != nil { 415 | log.Fatal(err) 416 | } 417 | 418 | for _, alb := range albs { 419 | fmt.Println(alb) 420 | } 421 | } 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-cert-utils 2 | 3 | Certificate Utility for AWS(ACM, IAM, ALB, ELB, CloudFront) 4 | 5 | ## Installation 6 | 7 | Download from https://github.com/tkuchiki/aws-cert-utils/releases 8 | 9 | ## Usage 10 | 11 | ```console 12 | usage: aws-cert-utils [] [ ...] 13 | 14 | Certificate Utility for AWS(ACM, IAM, ALB, ELB, CloudFront) 15 | 16 | Flags: 17 | --help Show context-sensitive help (also try --help-long 18 | and --help-man). 19 | --access-key=ACCESS-KEY The AWS access key ID 20 | --secret-key=SECRET-KEY The AWS secret access key 21 | --assume-role-arn=ASSUME-ROLE-ARN 22 | The AWS assume role ARN 23 | --token=TOKEN The AWS access token 24 | --region=REGION The AWS region 25 | --profile=PROFILE The AWS CLI profile 26 | --aws-config=AWS-CONFIG The AWS CLI Config file 27 | --credentials=CREDENTIALS The AWS CLI Credential file 28 | --version Show application version. 29 | 30 | Commands: 31 | help [...] 32 | Show help. 33 | 34 | acm list [] 35 | Retrieves a list of ACM Certificates and the domain name for each 36 | 37 | acm import [] 38 | Imports an SSL/TLS certificate into AWS Certificate Manager (ACM) to use 39 | with ACM's integrated AWS services 40 | 41 | acm delete [] 42 | Deletes an ACM Certificate and its associated private key 43 | 44 | iam list [] 45 | Lists the server certificates stored in IAM that have the specified path 46 | prefix 47 | 48 | iam upload [] 49 | Uploads a server certificate entity for the AWS account 50 | 51 | iam update [] 52 | Updates the name and/or the path of the specified server certificate stored 53 | in IAM 54 | 55 | iam delete [] 56 | Deletes the specified server certificate 57 | 58 | cloudfront list [] 59 | Lists the distributions 60 | 61 | cloudfront update [] 62 | Updates the configuration for a distribution 63 | 64 | cloudfront bulk-update [] 65 | Updates the configuration for distributions 66 | 67 | elb list [] 68 | Describes the specified the load balancers 69 | 70 | elb update [] 71 | Updates the specified a listener from the specified load balancer 72 | 73 | elb bulk-update [] 74 | Updates the specified listeners from the specified load balancer 75 | 76 | alb list [] 77 | Describes the specified load balancers 78 | 79 | alb update [] 80 | Updates the specified a listener from the specified load balancer 81 | 82 | alb bulk-update [] 83 | Updates the specified listeners from the specified load balancer 84 | ``` 85 | 86 | ### ACM 87 | 88 | ```console 89 | $ ./aws-cert-utils acm --help 90 | usage: aws-cert-utils acm [ ...] 91 | 92 | AWS Certificate Manager (ACM) 93 | 94 | Flags: 95 | --help Show context-sensitive help (also try --help-long and --help-man). 96 | --version Show application version. 97 | 98 | Subcommands: 99 | acm list [] 100 | Retrieves a list of ACM Certificates and the domain name for each 101 | 102 | acm import [] 103 | Imports an SSL/TLS certificate into AWS Certificate Manager (ACM) to use with ACM's integrated AWS services 104 | 105 | acm delete [] 106 | Deletes an ACM Certificate and its associated private key 107 | 108 | ``` 109 | 110 | #### List 111 | 112 | ```console 113 | $ ./aws-cert-utils acm list 114 | +------------------------+-----------------+-----------------+---------+-------------------------------+-------------------------------------------------------------------------------------+ 115 | | NAME TAG | DOMAIN NAME | ADDITIONAL NAME | IN USE? | NOT AFTER | CERTIFICATE ARN | 116 | +------------------------+-----------------+-----------------+---------+-------------------------------+-------------------------------------------------------------------------------------+ 117 | | | *.example.com | example.com | Yes | 2019-11-14 02:44:43 +0000 UTC | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 118 | +------------------------+ + + + +-------------------------------------------------------------------------------------+ 119 | | example.com | | | | | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy | 120 | +------------------------+-----------------+-----------------+---------+-------------------------------+-------------------------------------------------------------------------------------+ 121 | 122 | ``` 123 | 124 | #### Import 125 | 126 | ```console 127 | $ openssl rsa -in 4096key.pem -text -noout | head -n 1 128 | Private-Key: (4096 bit) 129 | 130 | $ ./aws-cert-utils acm import --cert-path 4096cert.pem --pkey-path 4096key.pem 131 | 2017/11/30 17:58:03 Invalid private key length (4096 bit). AWS supports 1024 and 2048 bit RSA private key 132 | 133 | $ ./aws-cert-utils acm import --cert-path cert.pem --pkey-path key.pem --chain-path ca.pem 134 | Imported arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz 135 | ``` 136 | 137 | #### Delete 138 | 139 | ```console 140 | $ ./aws-cert-utils acm delete 141 | ? Choose the server certificate you want to delete : [example.com] arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz 142 | Deleted arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz 143 | ``` 144 | 145 | ### IAM 146 | 147 | ```console 148 | $ ./aws-cert-utils iam --help 149 | usage: aws-cert-utils iam [ ...] 150 | 151 | AWS Identity and Access Management (IAM) 152 | 153 | Flags: 154 | --help Show context-sensitive help (also try --help-long and --help-man). 155 | --version Show application version. 156 | 157 | Subcommands: 158 | iam list 159 | Lists the server certificates stored in IAM that have the specified path prefix 160 | 161 | iam upload [] 162 | Uploads a server certificate entity for the AWS account 163 | 164 | iam update [] 165 | Updates the name and/or the path of the specified server certificate stored in IAM 166 | 167 | iam delete [] 168 | Deletes the specified server certificate 169 | 170 | ``` 171 | 172 | #### List 173 | 174 | ```console 175 | $ ./aws-cert-utils iam list 176 | +------------------------------+-----------------------+--------------------------------+-------------------------------------------------------------------------------------+ 177 | | NAME | ID | PATH | ARN | 178 | +------------------------------+-----------------------+--------------------------------+-------------------------------------------------------------------------------------+ 179 | | test-certificate | XXXXXXXXXXXXXXXXXXXXX | / | arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 180 | | test-cloudfront-certificate | YYYYYYYYYYYYYYYYYYYYY | /cloudfront/ | arn:aws:iam::xxxxxxxxxxxx:server-certificate/cloudfront/yyyyyyyyyyyyyyyyyyyyyyyyyyy | 181 | +------------------------------+-----------------------+--------------------------------+-------------------------------------------------------------------------------------+ 182 | ``` 183 | 184 | #### Upload 185 | 186 | ```console 187 | $ ./aws-cert-utils iam upload --cert-path cert.pem --chain-path ca.pem --pkey-path key.pem --path /cloudfront/ --name test-cert 188 | Uploaded test-cert arn:aws:iam::xxxxxxxxxxxx:server-certificate/cloudfront/yyyyyyyyyyyyyyyyyyyyyyyyyyy 189 | ``` 190 | 191 | #### Update 192 | 193 | ```console 194 | $ ./aws-cert-utils iam update --new-path / --new-name test-cert2 --name test-cert 195 | Updated test-cert -> test-cert2 196 | ``` 197 | 198 | #### Delete 199 | 200 | ```console 201 | $ ./aws-cert-utils iam delete 202 | ? Choose the server certificate you want to delete : test-cert2 203 | Deleted test-cert2 204 | ``` 205 | 206 | ### ALB 207 | 208 | ```console 209 | $ ./aws-cert-utils alb --help 210 | usage: aws-cert-utils alb [ ...] 211 | 212 | Application Load Balancing 213 | 214 | Flags: 215 | --help Show context-sensitive help (also try --help-long and --help-man). 216 | --version Show application version. 217 | 218 | Subcommands: 219 | alb list [] 220 | Describes the specified load balancers 221 | 222 | alb update [] 223 | Updates the specified a listener from the specified load balancer 224 | 225 | alb bulk-update [] 226 | Updates the specified listeners from the specified load balancer 227 | 228 | ``` 229 | 230 | #### List 231 | 232 | ```console 233 | $ ./aws-cert-utils alb list 234 | +-----------+------+-------------------------------------------------------------------------------------+ 235 | | NAME | PORT | LISTENER SSL CERTIFICATE | 236 | +-----------+------+-------------------------------------------------------------------------------------+ 237 | | test-alb | 443 | arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 238 | +-----------+------+-------------------------------------------------------------------------------------+ 239 | | test2-alb | 443 | arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 240 | +-----------+------+-------------------------------------------------------------------------------------+ 241 | ``` 242 | 243 | #### Update 244 | 245 | ```console 246 | $ ./aws-cert-utils alb update --name test-alb --cert-arn arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 247 | Updated test-alb:443 arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 248 | ``` 249 | 250 | #### Bulk update 251 | 252 | ```console 253 | $ ./aws-cert-utils alb list 254 | +-----------+------+-------------------------------------------------------------------------------------+ 255 | | NAME | PORT | LISTENER SSL CERTIFICATE | 256 | +-----------+------+-------------------------------------------------------------------------------------+ 257 | | test-alb | 443 | arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 258 | +-----------+------+-------------------------------------------------------------------------------------+ 259 | | test2-alb | 443 | arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 260 | +-----------+------+-------------------------------------------------------------------------------------+ 261 | 262 | $ ./aws-cert-utils alb bulk-update --source-cert-arn arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --dest-cert-arn arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 263 | # Dry run mode 264 | 265 | Updated test-alb:443 arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 266 | Updated test2-alb:443 arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 267 | 268 | $ ./aws-cert-utils alb bulk-update --source-cert-arn arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --dest-cert-arn arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --no-dry-run 269 | Updated test-alb:443 arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 270 | Updated test2-alb:443 arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 271 | 272 | $ ./aws-cert-utils alb list 273 | +-----------+------+-------------------------------------------------------------------------------------+ 274 | | NAME | PORT | LISTENER SSL CERTIFICATE | 275 | +-----------+------+-------------------------------------------------------------------------------------+ 276 | | test-alb | 443 | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 277 | +-----------+------+-------------------------------------------------------------------------------------+ 278 | | test2-alb | 443 | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 279 | +-----------+------+-------------------------------------------------------------------------------------+ 280 | ``` 281 | 282 | ### ELB 283 | 284 | ```console 285 | $ ./aws-cert-utils elb --help 286 | usage: aws-cert-utils elb [ ...] 287 | 288 | Elastic Load Balancing 289 | 290 | Flags: 291 | --help Show context-sensitive help (also try --help-long and --help-man). 292 | --version Show application version. 293 | 294 | Subcommands: 295 | elb list [] 296 | Describes the specified the load balancers 297 | 298 | elb update [] 299 | Updates the specified a listener from the specified load balancer 300 | 301 | elb bulk-update [] 302 | Updates the specified listeners from the specified load balancer 303 | 304 | ``` 305 | 306 | #### List 307 | 308 | ```console 309 | $ ./aws-cert-utils elb list 310 | +-----------+------+-------------------------------------------------------------------------------------+ 311 | | NAME | PORT | LISTENER SSL CERTIFICATE | 312 | +-----------+------+-------------------------------------------------------------------------------------+ 313 | | test-elb | 443 | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 314 | +-----------+------+-------------------------------------------------------------------------------------+ 315 | | test2-elb | 443 | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 316 | +-----------+------+-------------------------------------------------------------------------------------+ 317 | ``` 318 | 319 | #### Update 320 | 321 | ```console 322 | $ ./aws-cert-utils elb update --name test-elb --port 443 --cert-arn arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 323 | Updated test-elb:443 arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -> arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 324 | ``` 325 | 326 | #### Bulk update 327 | 328 | ```console 329 | $ ./aws-cert-utils elb list 330 | +-----------+------+-------------------------------------------------------------------------------------+ 331 | | NAME | PORT | LISTENER SSL CERTIFICATE | 332 | +-----------+------+-------------------------------------------------------------------------------------+ 333 | | test-elb | 443 | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 334 | +-----------+------+-------------------------------------------------------------------------------------+ 335 | | test2-elb | 443 | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 336 | +-----------+------+-------------------------------------------------------------------------------------+ 337 | 338 | $ ./aws-cert-utils elb bulk-update --source-cert-arn arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --dest-cert-arn arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 339 | # Dry run mode 340 | 341 | Updated test-elb:443 arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -> arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 342 | Updated test2-elb:443 arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -> arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 343 | 344 | $ ./aws-cert-utils elb bulk-update --source-cert-arn arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --dest-cert-arn arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --no-dry-run 345 | Updated test-elb:443 arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -> arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 346 | Updated test2-elb:443 arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -> arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 347 | 348 | $ ./aws-cert-utils elb list 349 | +-----------+------+-------------------------------------------------------------------------------------+ 350 | | NAME | PORT | LISTENER SSL CERTIFICATE | 351 | +-----------+------+-------------------------------------------------------------------------------------+ 352 | | test-elb | 443 | arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 353 | +-----------+------+-------------------------------------------------------------------------------------+ 354 | | test2-elb | 443 | arn:aws:iam::xxxxxxxxxxxx:server-certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 355 | +-----------+------+-------------------------------------------------------------------------------------+ 356 | ``` 357 | 358 | ### CloudFront 359 | 360 | ```console 361 | $ ./aws-cert-utils cloudfront --help 362 | usage: aws-cert-utils cloudfront [] [ ...] 363 | 364 | Amazon CloudFront 365 | 366 | Flags: 367 | --help Show context-sensitive help (also try --help-long and --help-man). 368 | --version Show application version. 369 | --max-items=100 The total number of items to return in the command's output 370 | 371 | Subcommands: 372 | cloudfront list [] 373 | Lists the distributions 374 | 375 | cloudfront update [] 376 | Updates the configuration for a distribution 377 | 378 | cloudfront bulk-update [] 379 | Updates the configuration for distributions 380 | 381 | ``` 382 | 383 | #### List 384 | 385 | ```console 386 | $ ./aws-cert-utils cloudfront list 387 | +-----------------+------------------------------+-------------------------------------------------------------------------------------+ 388 | | DISTRIBUTION ID | ALIASES | SSL CERTIFICATE | 389 | +-----------------+------------------------------+-------------------------------------------------------------------------------------+ 390 | | 11111111111111 | iam.example.com | XXXXXXXXXXXXXXXXXXXXX | test-cert-name | 391 | +-----------------+------------------------------+-------------------------------------------------------------------------------------+ 392 | | 22222222222222 | acm.example.com | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 393 | +-----------------+------------------------------+-------------------------------------------------------------------------------------+ 394 | ``` 395 | 396 | #### Update 397 | 398 | ```console 399 | $ ./aws-cert-utils cloudfront update --dist-id 11111111111111 --iam-id XXXXXXXXXXXXXXXXXXXXX 400 | Updated 11111111111111 iam.example.com XXXXXXXXXXXXXXXXXXXXX -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 401 | ``` 402 | 403 | #### Bulk update 404 | 405 | ```console 406 | $ ./aws-cert-utils cloudfront list 407 | +-----------------+------------------------------+-----------------------------------------------------------------+ 408 | | DISTRIBUTION ID | ALIASES | SSL CERTIFICATE | 409 | +-----------------+------------------------------+-----------------------------------------------------------------+ 410 | | 11111111111111 | iam.example.com | XXXXXXXXXXXXXXXXXXXXX | test-cert-name | 411 | +-----------------+------------------------------+-----------------------------------------------------------------+ 412 | | 22222222222222 | iam2.example.com | XXXXXXXXXXXXXXXXXXXXX | test-cert-name | 413 | +-----------------+------------------------------+-----------------------------------------------------------------+ 414 | 415 | $ ./aws-cert-utils cloudfront bulk-update --source-iam-id XXXXXXXXXXXXXXXXXXXXX --dest-acm-arn arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 416 | # Dry run mode 417 | 418 | Updated 11111111111111 iam.example.com XXXXXXXXXXXXXXXXXXXXX -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 419 | Updated 22222222222222 iam2.example.com XXXXXXXXXXXXXXXXXXXXX -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 420 | 421 | $ ./aws-cert-utils cloudfront bulk-update --source-iam-id XXXXXXXXXXXXXXXXXXXXX --dest-acm-arn arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --no-dry-run 422 | Updated 11111111111111 iam.example.com XXXXXXXXXXXXXXXXXXXXX -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 423 | Updated 22222222222222 iam2.example.com XXXXXXXXXXXXXXXXXXXXX -> arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 424 | 425 | $ ./aws-cert-utils cloudfront list 426 | +-----------------+------------------------------+-------------------------------------------------------------------------------------+ 427 | | DISTRIBUTION ID | ALIASES | SSL CERTIFICATE | 428 | +-----------------+------------------------------+-------------------------------------------------------------------------------------+ 429 | | 11111111111111 | iam.example.com | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 430 | +-----------------+------------------------------+-------------------------------------------------------------------------------------+ 431 | | 22222222222222 | iam2.example.com | arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | 432 | +-----------------+------------------------------+-------------------------------------------------------------------------------------+ 433 | ``` --------------------------------------------------------------------------------