1 | 'use strict'
|
2 |
|
3 | let co = require('co')
|
4 | let cli = require('heroku-cli-util')
|
5 |
|
6 | let openssl = require('../../lib/openssl.js')
|
7 | let endpoints = require('../../lib/endpoints.js').all
|
8 |
|
9 | function valEmpty (val) {
|
10 | if (val) {
|
11 | return val.length === 0
|
12 | } else {
|
13 | return true
|
14 | }
|
15 | }
|
16 |
|
17 | function getSubject (context) {
|
18 | let domain = context.args.domain
|
19 |
|
20 | let owner = context.flags.owner
|
21 | let country = context.flags.country
|
22 | let area = context.flags.area
|
23 | let city = context.flags.city
|
24 |
|
25 | let subject = context.flags.subject
|
26 |
|
27 | if (valEmpty(subject)) {
|
28 | subject = ''
|
29 | if (!valEmpty(country)) {
|
30 | subject += `/C=${country}`
|
31 | }
|
32 |
|
33 | if (!valEmpty(area)) {
|
34 | subject += `/ST=${area}`
|
35 | }
|
36 |
|
37 | if (!valEmpty(city)) {
|
38 | subject += `/L=${city}`
|
39 | }
|
40 |
|
41 | if (!valEmpty(owner)) {
|
42 | subject += `/O=${owner}`
|
43 | }
|
44 |
|
45 | subject += `/CN=${domain}`
|
46 | }
|
47 |
|
48 | return subject
|
49 | }
|
50 |
|
51 | function requiresPrompt (context) {
|
52 | if (valEmpty(context.flags.subject)) {
|
53 | let args = [context.flags.owner, context.flags.country, context.flags.area, context.flags.city]
|
54 | if (!context.flags.now && args.every(function (v) { return valEmpty(v) })) {
|
55 | return true
|
56 | }
|
57 | }
|
58 | return false
|
59 | }
|
60 |
|
61 | function getCommand (certs, domain) {
|
62 | if (certs.find(function (f) {
|
63 | return f.ssl_cert.cert_domains.find(function (d) {
|
64 | return d === domain
|
65 | })
|
66 | })) {
|
67 | return 'update'
|
68 | } else {
|
69 | return 'add'
|
70 | }
|
71 | }
|
72 |
|
73 | function * run (context, heroku) {
|
74 | if (requiresPrompt(context)) {
|
75 | context.flags.owner = yield cli.prompt('Owner of this certificate')
|
76 | context.flags.country = yield cli.prompt('Country of owner (two-letter ISO code)')
|
77 | context.flags.area = yield cli.prompt('State/province/etc. of owner')
|
78 | context.flags.city = yield cli.prompt('City of owner')
|
79 | }
|
80 |
|
81 | let subject = getSubject(context)
|
82 |
|
83 | let domain = context.args.domain
|
84 | let keysize = context.flags.keysize || 2048
|
85 | let keyfile = `${domain}.key`
|
86 |
|
87 | let certs = yield endpoints(context.app, heroku)
|
88 |
|
89 | var command = getCommand(certs, domain)
|
90 |
|
91 | if (context.flags.selfsigned) {
|
92 | let crtfile = `${domain}.crt`
|
93 |
|
94 | yield openssl.spawn(['req', '-new', '-newkey', `rsa:${keysize}`, '-nodes', '-keyout', keyfile, '-out', crtfile, '-subj', subject, '-x509'])
|
95 |
|
96 | cli.console.error('Your key and self-signed certificate have been generated.')
|
97 | cli.console.error('Next, run:')
|
98 | cli.console.error(`$ heroku certs:${command} ${crtfile} ${keyfile}`)
|
99 | } else {
|
100 | let csrfile = `${domain}.csr`
|
101 |
|
102 | yield openssl.spawn(['req', '-new', '-newkey', `rsa:${keysize}`, '-nodes', '-keyout', keyfile, '-out', csrfile, '-subj', subject])
|
103 |
|
104 | cli.console.error('Your key and certificate signing request have been generated.')
|
105 | cli.console.error(`Submit the CSR in '${csrfile}' to your preferred certificate authority.`)
|
106 | cli.console.error("When you've received your certificate, run:")
|
107 | cli.console.error(`$ heroku certs:${command} CERTFILE ${keyfile}`)
|
108 | }
|
109 | }
|
110 |
|
111 | module.exports = {
|
112 | topic: 'certs',
|
113 | command: 'generate',
|
114 | args: [
|
115 | {name: 'domain', optional: false}
|
116 | ],
|
117 | flags: [
|
118 | {
|
119 | name: 'selfsigned',
|
120 | optional: true,
|
121 | hasValue: false,
|
122 | description: 'generate a self-signed certificate instead of a CSR'
|
123 | }, {
|
124 | name: 'keysize',
|
125 | optional: true,
|
126 | hasValue: true,
|
127 | description: 'RSA key size in bits (default: 2048)'
|
128 | }, {
|
129 | name: 'owner',
|
130 | optional: true,
|
131 | hasValue: true,
|
132 | description: 'name of organization certificate belongs to'
|
133 | }, {
|
134 | name: 'country',
|
135 | optional: true,
|
136 | hasValue: true,
|
137 | description: 'country of owner, as a two-letter ISO country code'
|
138 | }, {
|
139 | name: 'area',
|
140 | optional: true,
|
141 | hasValue: true,
|
142 | description: 'sub-country area (state, province, etc.) of owner'
|
143 | }, {
|
144 | name: 'city',
|
145 | optional: true,
|
146 | hasValue: true,
|
147 | description: 'city of owner'
|
148 | }, {
|
149 | name: 'subject',
|
150 | optional: true,
|
151 | hasValue: true,
|
152 | description: 'specify entire certificate subject'
|
153 | }, {
|
154 | name: 'now',
|
155 | optional: true,
|
156 | hasValue: false,
|
157 | description: 'do not prompt for any owner information'
|
158 | }
|
159 | ],
|
160 | description: 'generate a key and a CSR or self-signed certificate',
|
161 | help: `Generate a key and certificate signing request (or self-signed certificate)\nfor an app. Prompts for information to put in the certificate unless --now\nis used, or at least one of the --subject, --owner, --country, --area, or\n--city options is specified.
|
162 |
|
163 | Example:
|
164 |
|
165 | $ heroku certs:generate example.com
|
166 | `,
|
167 | needsApp: true,
|
168 | needsAuth: true,
|
169 | run: cli.command(co.wrap(run))
|
170 | }
|