-
Notifications
You must be signed in to change notification settings - Fork 228
/
Copy pathadd.ts
111 lines (95 loc) · 3.75 KB
/
add.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import color from '@heroku-cli/color'
import {APIClient, Command, flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import * as Heroku from '@heroku-cli/schema'
import {waitForDomains} from '../../lib/certs/domains'
import {prompt} from 'inquirer'
import {getCertAndKey} from '../../lib/certs/get_cert_and_key'
import heredoc from 'tsheredoc'
import {SniEndpoint} from '../../lib/types/sni_endpoint'
import {displayCertificateDetails} from '../../lib/certs/certificate_details'
async function configureDomains(app: string, heroku: APIClient, cert: SniEndpoint) {
const certDomains = cert.ssl_cert.cert_domains
const apiDomains = await waitForDomains(app, heroku)
const appDomains = apiDomains?.map((domain: Heroku.Domain) => domain.hostname as string)
const matchedDomains = matchDomains(certDomains, appDomains ?? [])
if (matchedDomains.length > 0) {
ux.styledHeader('Almost done! Which of these domains on this application would you like this certificate associated with?')
const selections = await prompt<{domains: string[]}>([{
type: 'checkbox',
name: 'domains',
message: 'Select domains',
choices: matchedDomains,
}])
await Promise.all(selections?.domains.map(domain => {
return heroku.patch(`/apps/${app}/domains/${domain}`, {
body: {sni_endpoint: cert.name},
})
}))
}
}
export default class Add extends Command {
static topic = 'certs'
static strict = true
static description = `Add an SSL certificate to an app.
Note: certificates with PEM encoding are also valid.
`
static examples = [
heredoc(`$ heroku certs:add example.com.crt example.com.key
If you require intermediate certificates, refer to this article on merging certificates to get a complete chain:
https://help.salesforce.com/s/articleView?id=000333504&type=1`),
]
static flags = {
app: flags.app({required: true}),
remote: flags.remote(),
}
static args = {
CRT: Args.string({required: true, description: 'absolute path of the certificate file on disk'}),
KEY: Args.string({required: true, description: 'absolute path of the key file on disk'}),
}
public async run(): Promise<void> {
const {flags, args} = await this.parse(Add)
const {app} = flags
const files = await getCertAndKey(args)
ux.action.start(`Adding SSL certificate to ${color.magenta(app)}`)
const {body: sniEndpoint} = await this.heroku.post<SniEndpoint>(`/apps/${app}/sni-endpoints`, {
body: {
certificate_chain: files.crt.toString(),
private_key: files.key.toString(),
},
})
ux.action.stop()
displayCertificateDetails(sniEndpoint)
await configureDomains(app, this.heroku, sniEndpoint)
}
}
function splitDomains(domains: string[]): [string, string][] {
return domains.map(domain => {
return [domain.slice(0, 1), domain.slice(1)]
})
}
function createMatcherFromSplitDomain([firstChar, rest]: [string, string]) {
const matcherContents = []
if (firstChar === '*') {
matcherContents.push('^[\\w\\-]+')
} else {
matcherContents.push(firstChar)
}
const escapedRest = rest.replace(/\./g, '\\.')
matcherContents.push(escapedRest)
return new RegExp(matcherContents.join(''))
}
function matchDomains(certDomains: string[], appDomains: string[]) {
const splitCertDomains = splitDomains(certDomains)
const matchers = splitCertDomains.map(splitDomain => createMatcherFromSplitDomain(splitDomain))
if (splitCertDomains.some(domain => (domain[0] === '*'))) {
const matchedDomains: string[] = []
appDomains.forEach(appDomain => {
if (matchers.some(matcher => matcher.test(appDomain))) {
matchedDomains.push(appDomain)
}
})
return matchedDomains
}
return certDomains.filter(domain => appDomains.includes(domain))
}