@salesforce/b2c-cli 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/ecdn/mtls/create.d.ts +21 -5
- package/dist/commands/ecdn/mtls/create.js +106 -35
- package/dist/commands/ecdn/mtls/create.js.map +1 -1
- package/dist/commands/ecdn/mtls/issue.d.ts +46 -0
- package/dist/commands/ecdn/mtls/issue.js +95 -0
- package/dist/commands/ecdn/mtls/issue.js.map +1 -0
- package/dist/commands/ecdn/mtls/setup.d.ts +55 -0
- package/dist/commands/ecdn/mtls/setup.js +165 -0
- package/dist/commands/ecdn/mtls/setup.js.map +1 -0
- package/dist/utils/ecdn/mtls.d.ts +80 -0
- package/dist/utils/ecdn/mtls.js +164 -0
- package/dist/utils/ecdn/mtls.js.map +1 -0
- package/oclif.manifest.json +18210 -17686
- package/package.json +2 -2
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MtlsCertificate } from '@salesforce/b2c-tooling-sdk/operations/mtls';
|
|
2
2
|
import { EcdnCommand } from '../../../utils/ecdn/index.js';
|
|
3
|
-
type
|
|
3
|
+
import { type CodeUploadCertificateFiles } from '../../../utils/ecdn/mtls.js';
|
|
4
4
|
/**
|
|
5
5
|
* Response type for the create command.
|
|
6
6
|
*/
|
|
7
7
|
interface CreateOutput {
|
|
8
|
-
|
|
8
|
+
/** Expiry of the generated CA (only with --generate) */
|
|
9
|
+
caExpiresOn?: string;
|
|
10
|
+
certificate: MtlsCertificate;
|
|
11
|
+
/** Passphrase for the generated client certificate (only with --generate) */
|
|
12
|
+
clientPassphrase?: string;
|
|
13
|
+
/** Generated files (only with --generate) */
|
|
14
|
+
files?: CodeUploadCertificateFiles;
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
11
17
|
* Command to create an mTLS certificate for code upload.
|
|
@@ -16,8 +22,16 @@ export default class EcdnMtlsCreate extends EcdnCommand<typeof EcdnMtlsCreate> {
|
|
|
16
22
|
static examples: string[];
|
|
17
23
|
static flags: {
|
|
18
24
|
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
-
'certificate-file': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
-
'private-key-file': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
25
|
+
'certificate-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
|
+
'private-key-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
+
generate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
28
|
+
'out-dir': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
|
+
'client-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
|
+
'p12-passphrase': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
31
|
+
'ca-common-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
32
|
+
'ca-days': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
33
|
+
'client-days': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
34
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
21
35
|
'client-id': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
36
|
'client-secret': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
37
|
'auth-scope': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -42,5 +56,7 @@ export default class EcdnMtlsCreate extends EcdnCommand<typeof EcdnMtlsCreate> {
|
|
|
42
56
|
'extra-headers': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
43
57
|
};
|
|
44
58
|
run(): Promise<CreateOutput>;
|
|
59
|
+
private generate;
|
|
60
|
+
private upload;
|
|
45
61
|
}
|
|
46
62
|
export {};
|
|
@@ -6,65 +6,80 @@
|
|
|
6
6
|
import { Flags, ux } from '@oclif/core';
|
|
7
7
|
import cliui from 'cliui';
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
|
-
import {
|
|
9
|
+
import { createCodeUploadCertificate, MAX_CODE_UPLOAD_CA_VALIDITY_DAYS, } from '@salesforce/b2c-tooling-sdk/operations/mtls';
|
|
10
|
+
import { EcdnCommand } from '../../../utils/ecdn/index.js';
|
|
11
|
+
import { caKeySecurityNotice, clientCertificateNotice, defaultCaCommonName, generateAndUploadCodeUploadCertificate, stagingOrganizationWarning, } from '../../../utils/ecdn/mtls.js';
|
|
10
12
|
import { t, withDocs } from '../../../i18n/index.js';
|
|
11
13
|
/**
|
|
12
14
|
* Command to create an mTLS certificate for code upload.
|
|
13
15
|
*/
|
|
14
16
|
export default class EcdnMtlsCreate extends EcdnCommand {
|
|
15
|
-
static description = withDocs(t('commands.ecdn.mtls.create.description', '
|
|
17
|
+
static description = withDocs(t('commands.ecdn.mtls.create.description', 'Register a CA certificate for two-factor (mTLS) code upload; with --generate, also create the CA and issue a first client certificate'), '/cli/ecdn.html#b2c-ecdn-mtls-create');
|
|
16
18
|
static enableJsonFlag = true;
|
|
17
19
|
static examples = [
|
|
18
20
|
'<%= config.bin %> <%= command.id %> --tenant-id zzxy_prd --name my-cert --certificate-file cert.pem --private-key-file key.pem',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> --tenant-id zzxy_prd --name my-cert --generate',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> --tenant-id zzxy_prd --name my-cert --generate --out-dir ./certs --client-name build-server',
|
|
19
23
|
];
|
|
20
24
|
static flags = {
|
|
21
25
|
...EcdnCommand.baseFlags,
|
|
22
26
|
name: Flags.string({
|
|
23
|
-
description: t('flags.name.description', '
|
|
27
|
+
description: t('flags.name.description', 'Name (label) for the registered CA certificate'),
|
|
24
28
|
required: true,
|
|
25
29
|
}),
|
|
26
30
|
'certificate-file': Flags.string({
|
|
27
|
-
description: t('flags.certificateFile.description', 'Path to PEM-encoded certificate file'),
|
|
28
|
-
|
|
31
|
+
description: t('flags.certificateFile.description', 'Path to PEM-encoded CA certificate file'),
|
|
32
|
+
exactlyOne: ['certificate-file', 'generate'],
|
|
33
|
+
dependsOn: ['private-key-file'],
|
|
29
34
|
}),
|
|
30
35
|
'private-key-file': Flags.string({
|
|
31
|
-
description: t('flags.privateKeyFile.description', 'Path to PEM-encoded private key file'),
|
|
32
|
-
|
|
36
|
+
description: t('flags.privateKeyFile.description', 'Path to PEM-encoded CA private key file'),
|
|
37
|
+
dependsOn: ['certificate-file'],
|
|
38
|
+
}),
|
|
39
|
+
generate: Flags.boolean({
|
|
40
|
+
description: t('flags.generate.description', 'Generate a new CA, upload it, and issue a client certificate (.p12) for code upload'),
|
|
41
|
+
exactlyOne: ['certificate-file', 'generate'],
|
|
42
|
+
}),
|
|
43
|
+
'out-dir': Flags.string({
|
|
44
|
+
description: t('flags.outDir.description', 'Directory for generated files (with --generate)'),
|
|
45
|
+
default: 'mtls-certs',
|
|
46
|
+
}),
|
|
47
|
+
'client-name': Flags.string({
|
|
48
|
+
description: t('flags.clientName.description', 'Name of the client certificate (.p12) issued from the new CA, used for code upload (with --generate; default: <name>-client)'),
|
|
49
|
+
}),
|
|
50
|
+
'p12-passphrase': Flags.string({
|
|
51
|
+
description: t('flags.p12Passphrase.description', 'Passphrase for the generated .p12 (with --generate; default: random)'),
|
|
52
|
+
env: 'SFCC_MTLS_P12_PASSPHRASE',
|
|
53
|
+
}),
|
|
54
|
+
'ca-common-name': Flags.string({
|
|
55
|
+
description: t('flags.caCommonName.description', 'Common name for the generated CA (with --generate; default: the staging hostname if configured, else "<name> CA")'),
|
|
56
|
+
}),
|
|
57
|
+
'ca-days': Flags.integer({
|
|
58
|
+
description: t('flags.caDays.description', 'Validity of the generated CA certificate in days (with --generate; eCDN allows at most 365)'),
|
|
59
|
+
default: MAX_CODE_UPLOAD_CA_VALIDITY_DAYS,
|
|
60
|
+
min: 1,
|
|
61
|
+
max: MAX_CODE_UPLOAD_CA_VALIDITY_DAYS,
|
|
62
|
+
}),
|
|
63
|
+
'client-days': Flags.integer({
|
|
64
|
+
description: t('flags.clientDays.description', 'Validity of the generated client certificate in days (with --generate; capped at the CA expiry)'),
|
|
65
|
+
default: 365,
|
|
66
|
+
min: 1,
|
|
67
|
+
}),
|
|
68
|
+
force: Flags.boolean({
|
|
69
|
+
description: t('flags.force.description', 'Overwrite existing generated files'),
|
|
70
|
+
default: false,
|
|
33
71
|
}),
|
|
34
72
|
};
|
|
35
73
|
async run() {
|
|
36
74
|
this.requireOAuthCredentials();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const privateKey = fs.readFileSync(this.flags['private-key-file'], 'utf8');
|
|
42
|
-
const body = {
|
|
43
|
-
name: this.flags.name,
|
|
44
|
-
certificate,
|
|
45
|
-
privateKey,
|
|
46
|
-
};
|
|
47
|
-
const client = this.getCdnZonesRwClient();
|
|
48
|
-
const organizationId = this.getOrganizationId();
|
|
49
|
-
const { data, error } = await client.POST('/organizations/{organizationId}/mtls/code-upload-certificates', {
|
|
50
|
-
params: {
|
|
51
|
-
path: { organizationId },
|
|
52
|
-
},
|
|
53
|
-
body,
|
|
54
|
-
});
|
|
55
|
-
if (error) {
|
|
56
|
-
this.error(t('commands.ecdn.mtls.create.error', 'Failed to create mTLS certificate: {{message}}', {
|
|
57
|
-
message: formatApiError(error),
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
const cert = data?.data;
|
|
61
|
-
if (!cert) {
|
|
62
|
-
this.error(t('commands.ecdn.mtls.create.noData', 'No certificate data returned from API'));
|
|
63
|
-
}
|
|
64
|
-
const output = { certificate: cert };
|
|
75
|
+
const stagingWarning = stagingOrganizationWarning(this.getOrganizationId());
|
|
76
|
+
if (stagingWarning)
|
|
77
|
+
this.warn(stagingWarning);
|
|
78
|
+
const output = this.flags.generate ? await this.generate() : await this.upload();
|
|
65
79
|
if (this.jsonEnabled()) {
|
|
66
80
|
return output;
|
|
67
81
|
}
|
|
82
|
+
const cert = output.certificate;
|
|
68
83
|
const ui = cliui({ width: process.stdout.columns || 80 });
|
|
69
84
|
const labelWidth = 20;
|
|
70
85
|
ui.div('');
|
|
@@ -78,7 +93,63 @@ export default class EcdnMtlsCreate extends EcdnCommand {
|
|
|
78
93
|
ui.div({ text: 'Hostname:', width: labelWidth }, { text: cert.mtlsAssociatedCodeUploadHostname });
|
|
79
94
|
}
|
|
80
95
|
ux.stdout(ui.toString());
|
|
96
|
+
if (output.files && output.clientPassphrase) {
|
|
97
|
+
ux.stdout(`CA certificate (uploaded to eCDN): ${output.files.caCertificate}`);
|
|
98
|
+
ux.stdout('');
|
|
99
|
+
ux.stdout(caKeySecurityNotice(output.files.caPrivateKey, output.caExpiresOn));
|
|
100
|
+
ux.stdout('');
|
|
101
|
+
ux.stdout(clientCertificateNotice({
|
|
102
|
+
file: output.files.clientCertificate,
|
|
103
|
+
hostname: cert.mtlsAssociatedCodeUploadHostname,
|
|
104
|
+
passphrase: output.clientPassphrase,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
ux.stdout(t('commands.ecdn.mtls.create.issueHint', 'Issue client certificates (.p12) signed by this CA for code upload with "b2c ecdn mtls issue".'));
|
|
109
|
+
}
|
|
81
110
|
return output;
|
|
82
111
|
}
|
|
112
|
+
async generate() {
|
|
113
|
+
const { flags } = this;
|
|
114
|
+
try {
|
|
115
|
+
return await generateAndUploadCodeUploadCertificate({
|
|
116
|
+
client: this.getCdnZonesRwClient(),
|
|
117
|
+
organizationId: this.getOrganizationId(),
|
|
118
|
+
name: flags.name,
|
|
119
|
+
caCommonName: flags['ca-common-name'] ?? defaultCaCommonName(flags.name, this.resolvedConfig.values.hostname),
|
|
120
|
+
clientName: flags['client-name'] ?? `${flags.name}-client`,
|
|
121
|
+
outDir: flags['out-dir'],
|
|
122
|
+
passphrase: flags['p12-passphrase'],
|
|
123
|
+
caValidityDays: flags['ca-days'],
|
|
124
|
+
clientValidityDays: flags['client-days'],
|
|
125
|
+
force: flags.force,
|
|
126
|
+
onProgress: (message) => {
|
|
127
|
+
if (!this.jsonEnabled())
|
|
128
|
+
this.log(message);
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async upload() {
|
|
137
|
+
if (!this.jsonEnabled()) {
|
|
138
|
+
this.log(t('commands.ecdn.mtls.create.creating', 'Creating mTLS certificate...'));
|
|
139
|
+
}
|
|
140
|
+
const certificatePem = fs.readFileSync(this.flags['certificate-file'], 'utf8');
|
|
141
|
+
const privateKeyPem = fs.readFileSync(this.flags['private-key-file'], 'utf8');
|
|
142
|
+
try {
|
|
143
|
+
const certificate = await createCodeUploadCertificate(this.getCdnZonesRwClient(), this.getOrganizationId(), {
|
|
144
|
+
name: this.flags.name,
|
|
145
|
+
certificatePem,
|
|
146
|
+
privateKeyPem,
|
|
147
|
+
});
|
|
148
|
+
return { certificate };
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
83
154
|
}
|
|
84
155
|
//# sourceMappingURL=create.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../../src/commands/ecdn/mtls/create.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAC,KAAK,EAAE,EAAE,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../../src/commands/ecdn/mtls/create.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BaseCommand } from '@salesforce/b2c-tooling-sdk/cli';
|
|
2
|
+
/**
|
|
3
|
+
* Response type for the issue command.
|
|
4
|
+
*/
|
|
5
|
+
interface IssueOutput {
|
|
6
|
+
/** Path to the written PKCS12 file */
|
|
7
|
+
file: string;
|
|
8
|
+
/** Certificate expiry */
|
|
9
|
+
notAfter: string;
|
|
10
|
+
/** Passphrase protecting the PKCS12 file */
|
|
11
|
+
passphrase: string;
|
|
12
|
+
/** Hex serial number */
|
|
13
|
+
serialNumber: string;
|
|
14
|
+
/** Certificate subject */
|
|
15
|
+
subject: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Command to issue a client certificate (.p12) for mTLS code upload from an existing CA.
|
|
19
|
+
*/
|
|
20
|
+
export default class EcdnMtlsIssue extends BaseCommand<typeof EcdnMtlsIssue> {
|
|
21
|
+
static description: string;
|
|
22
|
+
static enableJsonFlag: boolean;
|
|
23
|
+
static examples: string[];
|
|
24
|
+
static flags: {
|
|
25
|
+
'ca-cert-file': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
|
+
'ca-key-file': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
+
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
|
+
'p12-passphrase': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
|
+
days: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
31
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
32
|
+
'log-level': import("@oclif/core/interfaces").OptionFlag<"trace" | "debug" | "info" | "warn" | "error" | "silent" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
33
|
+
debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
34
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
35
|
+
jsonl: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
36
|
+
lang: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
37
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
38
|
+
instance: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
39
|
+
'project-directory': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
40
|
+
'extra-query': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
41
|
+
'extra-body': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
42
|
+
'extra-headers': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
43
|
+
};
|
|
44
|
+
run(): Promise<IssueOutput>;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { Flags, ux } from '@oclif/core';
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { BaseCommand } from '@salesforce/b2c-tooling-sdk/cli';
|
|
10
|
+
import { issueClientCertificate } from '@salesforce/b2c-tooling-sdk/operations/mtls';
|
|
11
|
+
import { assertFilesWritable, clientCertificateNotice, toFileStem, writePrivateFile } from '../../../utils/ecdn/mtls.js';
|
|
12
|
+
import { t, withDocs } from '../../../i18n/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* Command to issue a client certificate (.p12) for mTLS code upload from an existing CA.
|
|
15
|
+
*/
|
|
16
|
+
export default class EcdnMtlsIssue extends BaseCommand {
|
|
17
|
+
static description = withDocs(t('commands.ecdn.mtls.issue.description', 'Issue a client certificate (.p12) for mTLS code upload, signed by an existing CA (runs locally)'), '/cli/ecdn.html#b2c-ecdn-mtls-issue');
|
|
18
|
+
static enableJsonFlag = true;
|
|
19
|
+
static examples = [
|
|
20
|
+
'<%= config.bin %> <%= command.id %> --ca-cert-file mtls-certs/ca.pem --ca-key-file mtls-certs/ca.key --name jane.doe',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> --ca-cert-file ca.pem --ca-key-file ca.key --name ci --output ci.p12 --days 90',
|
|
22
|
+
];
|
|
23
|
+
static flags = {
|
|
24
|
+
...BaseCommand.baseFlags,
|
|
25
|
+
'ca-cert-file': Flags.string({
|
|
26
|
+
description: t('flags.caCertFile.description', 'Path to PEM-encoded CA certificate file'),
|
|
27
|
+
required: true,
|
|
28
|
+
}),
|
|
29
|
+
'ca-key-file': Flags.string({
|
|
30
|
+
description: t('flags.caKeyFile.description', 'Path to PEM-encoded CA private key file'),
|
|
31
|
+
required: true,
|
|
32
|
+
}),
|
|
33
|
+
name: Flags.string({
|
|
34
|
+
description: t('flags.clientCommonName.description', 'Common name identifying the client (user or pipeline)'),
|
|
35
|
+
required: true,
|
|
36
|
+
}),
|
|
37
|
+
output: Flags.string({
|
|
38
|
+
char: 'o',
|
|
39
|
+
description: t('flags.p12Output.description', 'Output path for the .p12 file (default: <name>.p12 next to the CA)'),
|
|
40
|
+
}),
|
|
41
|
+
'p12-passphrase': Flags.string({
|
|
42
|
+
description: t('flags.p12Passphrase.description', 'Passphrase for the .p12 (default: random)'),
|
|
43
|
+
env: 'SFCC_MTLS_P12_PASSPHRASE',
|
|
44
|
+
}),
|
|
45
|
+
days: Flags.integer({
|
|
46
|
+
description: t('flags.clientDays.description', 'Validity of the client certificate in days'),
|
|
47
|
+
default: 365,
|
|
48
|
+
min: 1,
|
|
49
|
+
}),
|
|
50
|
+
force: Flags.boolean({
|
|
51
|
+
description: t('flags.force.description', 'Overwrite an existing output file'),
|
|
52
|
+
default: false,
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
async run() {
|
|
56
|
+
const { flags } = this;
|
|
57
|
+
const file = path.resolve(flags.output ?? path.join(path.dirname(flags['ca-cert-file']), `${toFileStem(flags.name)}.p12`));
|
|
58
|
+
let issued;
|
|
59
|
+
try {
|
|
60
|
+
assertFilesWritable([file], flags.force);
|
|
61
|
+
issued = issueClientCertificate({
|
|
62
|
+
ca: {
|
|
63
|
+
certificatePem: fs.readFileSync(flags['ca-cert-file'], 'utf8'),
|
|
64
|
+
privateKeyPem: fs.readFileSync(flags['ca-key-file'], 'utf8'),
|
|
65
|
+
},
|
|
66
|
+
commonName: flags.name,
|
|
67
|
+
validityDays: flags.days,
|
|
68
|
+
passphrase: flags['p12-passphrase'],
|
|
69
|
+
});
|
|
70
|
+
writePrivateFile(file, issued.pkcs12);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
74
|
+
}
|
|
75
|
+
const output = {
|
|
76
|
+
file,
|
|
77
|
+
passphrase: issued.passphrase,
|
|
78
|
+
subject: issued.subject,
|
|
79
|
+
serialNumber: issued.serialNumber,
|
|
80
|
+
notAfter: issued.notAfter.toISOString(),
|
|
81
|
+
};
|
|
82
|
+
if (this.jsonEnabled()) {
|
|
83
|
+
return output;
|
|
84
|
+
}
|
|
85
|
+
ux.stdout(t('commands.ecdn.mtls.issue.success', 'Client certificate issued successfully!'));
|
|
86
|
+
ux.stdout(` Subject: ${output.subject}`);
|
|
87
|
+
ux.stdout(` Expires: ${output.notAfter}`);
|
|
88
|
+
ux.stdout('');
|
|
89
|
+
ux.stdout(clientCertificateNotice({ file: output.file, passphrase: output.passphrase }));
|
|
90
|
+
ux.stdout('');
|
|
91
|
+
ux.stdout(t('commands.ecdn.mtls.issue.caReminder', 'Reminder: return the CA private key to secure storage when you are done issuing certificates.'));
|
|
92
|
+
return output;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=issue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../../src/commands/ecdn/mtls/issue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { MtlsCertificate } from '@salesforce/b2c-tooling-sdk/operations/mtls';
|
|
2
|
+
import { EcdnCommand } from '../../../utils/ecdn/index.js';
|
|
3
|
+
import { type CodeUploadCertificateFiles } from '../../../utils/ecdn/mtls.js';
|
|
4
|
+
/**
|
|
5
|
+
* Response type for the setup command.
|
|
6
|
+
*/
|
|
7
|
+
interface SetupOutput {
|
|
8
|
+
certificate?: MtlsCertificate;
|
|
9
|
+
clientPassphrase?: string;
|
|
10
|
+
/** Path of dw.json if it was updated */
|
|
11
|
+
configUpdated?: string;
|
|
12
|
+
created: boolean;
|
|
13
|
+
files?: CodeUploadCertificateFiles;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Interactive wizard for end-to-end mTLS code upload certificate setup.
|
|
17
|
+
*/
|
|
18
|
+
export default class EcdnMtlsSetup extends EcdnCommand<typeof EcdnMtlsSetup> {
|
|
19
|
+
static description: string;
|
|
20
|
+
static enableJsonFlag: boolean;
|
|
21
|
+
static examples: string[];
|
|
22
|
+
static flags: {
|
|
23
|
+
'p12-passphrase': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
24
|
+
'client-id': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
25
|
+
'client-secret': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
|
+
'auth-scope': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
+
'short-code': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
|
+
'tenant-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
|
+
'auth-methods': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
|
+
'user-auth': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
31
|
+
'account-manager-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
32
|
+
'jwt-cert': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
33
|
+
'jwt-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
34
|
+
'jwt-passphrase': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
35
|
+
'log-level': import("@oclif/core/interfaces").OptionFlag<"trace" | "debug" | "info" | "warn" | "error" | "silent" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
36
|
+
debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
37
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
38
|
+
jsonl: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
39
|
+
lang: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
40
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
41
|
+
instance: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
42
|
+
'project-directory': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
43
|
+
'extra-query': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
44
|
+
'extra-body': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
45
|
+
'extra-headers': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
46
|
+
};
|
|
47
|
+
run(): Promise<SetupOutput>;
|
|
48
|
+
/**
|
|
49
|
+
* Returns `false` if no generated files exist, `true` if the user agreed to overwrite, or `undefined` to cancel.
|
|
50
|
+
*/
|
|
51
|
+
private confirmOverwrite;
|
|
52
|
+
private listExisting;
|
|
53
|
+
private maybeUpdateConfig;
|
|
54
|
+
}
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { Flags, ux } from '@oclif/core';
|
|
7
|
+
import { confirm, input } from '@inquirer/prompts';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { updateInstanceConfig } from '@salesforce/b2c-tooling-sdk/config';
|
|
11
|
+
import { EcdnCommand } from '../../../utils/ecdn/index.js';
|
|
12
|
+
import { caKeySecurityNotice, clientCertificateNotice, defaultCaCommonName, generateAndUploadCodeUploadCertificate, stagingOrganizationWarning, } from '../../../utils/ecdn/mtls.js';
|
|
13
|
+
import { t, withDocs } from '../../../i18n/index.js';
|
|
14
|
+
/**
|
|
15
|
+
* Interactive wizard for end-to-end mTLS code upload certificate setup.
|
|
16
|
+
*/
|
|
17
|
+
export default class EcdnMtlsSetup extends EcdnCommand {
|
|
18
|
+
static description = withDocs(t('commands.ecdn.mtls.setup.description', 'Guided version of "ecdn mtls create --generate": prompts for names with defaults, generates and registers a CA, issues a client certificate, and optionally configures dw.json'), '/cli/ecdn.html#b2c-ecdn-mtls-setup');
|
|
19
|
+
static enableJsonFlag = true;
|
|
20
|
+
static examples = ['<%= config.bin %> <%= command.id %> --tenant-id zzxy_prd'];
|
|
21
|
+
static flags = {
|
|
22
|
+
...EcdnCommand.baseFlags,
|
|
23
|
+
'p12-passphrase': Flags.string({
|
|
24
|
+
description: t('flags.p12Passphrase.description', 'Passphrase for the generated .p12 (default: random)'),
|
|
25
|
+
env: 'SFCC_MTLS_P12_PASSPHRASE',
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
async run() {
|
|
29
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
30
|
+
this.error(t('commands.ecdn.mtls.setup.notInteractive', 'This command is interactive. For non-interactive use, run "b2c ecdn mtls create --generate".'));
|
|
31
|
+
}
|
|
32
|
+
this.requireOAuthCredentials();
|
|
33
|
+
const stagingWarning = stagingOrganizationWarning(this.getOrganizationId());
|
|
34
|
+
if (stagingWarning)
|
|
35
|
+
this.warn(stagingWarning);
|
|
36
|
+
const existing = await this.listExisting();
|
|
37
|
+
if (existing.length > 0) {
|
|
38
|
+
ux.stdout(t('commands.ecdn.mtls.setup.existing', 'Existing code upload certificates:'));
|
|
39
|
+
for (const cert of existing) {
|
|
40
|
+
ux.stdout(` - ${cert.mtlsCertificateName ?? cert.mtlsCertificateId} (expires ${cert.expiresOn ?? '-'}, hostname ${cert.mtlsAssociatedCodeUploadHostname ?? '-'})`);
|
|
41
|
+
}
|
|
42
|
+
ux.stdout('');
|
|
43
|
+
ux.stdout(t('commands.ecdn.mtls.setup.rotationHint', 'Create a new CA to rotate an expiring or compromised one: keep the old CA until every client certificate is re-issued from the new CA, then remove it with "b2c ecdn mtls delete".'));
|
|
44
|
+
const proceed = await confirm({
|
|
45
|
+
message: t('commands.ecdn.mtls.setup.createAnother', 'Create a new CA certificate?'),
|
|
46
|
+
default: false,
|
|
47
|
+
});
|
|
48
|
+
if (!proceed) {
|
|
49
|
+
ux.stdout(t('commands.ecdn.mtls.setup.useIssue', 'To add a client certificate for an existing CA, use "b2c ecdn mtls issue".'));
|
|
50
|
+
return { created: false };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const name = await input({
|
|
54
|
+
message: t('commands.ecdn.mtls.setup.namePrompt', 'CA name (a label shown in eCDN and Business Manager):'),
|
|
55
|
+
default: 'code-upload',
|
|
56
|
+
validate: (v) => v.trim().length > 0 || 'Name is required',
|
|
57
|
+
});
|
|
58
|
+
const clientName = await input({
|
|
59
|
+
message: t('commands.ecdn.mtls.setup.clientNamePrompt', 'Client certificate name, for the .p12 you upload with (e.g. github-actions for a CI pipeline, or your Business Manager username):'),
|
|
60
|
+
default: 'ci',
|
|
61
|
+
validate: (v) => v.trim().length > 0 || 'Name is required',
|
|
62
|
+
});
|
|
63
|
+
const outDir = await input({
|
|
64
|
+
message: t('commands.ecdn.mtls.setup.outDirPrompt', 'Directory for generated files:'),
|
|
65
|
+
default: 'mtls-certs',
|
|
66
|
+
});
|
|
67
|
+
const force = await this.confirmOverwrite(outDir);
|
|
68
|
+
if (force === undefined) {
|
|
69
|
+
return { created: false };
|
|
70
|
+
}
|
|
71
|
+
const result = await generateAndUploadCodeUploadCertificate({
|
|
72
|
+
client: this.getCdnZonesRwClient(),
|
|
73
|
+
organizationId: this.getOrganizationId(),
|
|
74
|
+
name: name.trim(),
|
|
75
|
+
caCommonName: defaultCaCommonName(name.trim(), this.resolvedConfig.values.hostname),
|
|
76
|
+
clientName: clientName.trim(),
|
|
77
|
+
outDir,
|
|
78
|
+
passphrase: this.flags['p12-passphrase'],
|
|
79
|
+
force,
|
|
80
|
+
onProgress: (message) => {
|
|
81
|
+
if (!this.jsonEnabled())
|
|
82
|
+
this.log(message);
|
|
83
|
+
},
|
|
84
|
+
}).catch((error) => this.error(error instanceof Error ? error.message : String(error)));
|
|
85
|
+
const output = { created: true, ...result };
|
|
86
|
+
output.configUpdated = await this.maybeUpdateConfig(result.files, result.clientPassphrase);
|
|
87
|
+
const cert = result.certificate;
|
|
88
|
+
ux.stdout('');
|
|
89
|
+
ux.stdout(ux.colorize('green', t('commands.ecdn.mtls.setup.success', 'Two-factor code upload is set up.')));
|
|
90
|
+
ux.stdout(` Certificate ID: ${cert.mtlsCertificateId ?? '-'}`);
|
|
91
|
+
ux.stdout(` Hostname: ${cert.mtlsAssociatedCodeUploadHostname ?? '-'}`);
|
|
92
|
+
ux.stdout(` CA certificate: ${result.files.caCertificate}`);
|
|
93
|
+
ux.stdout('');
|
|
94
|
+
ux.stdout(caKeySecurityNotice(result.files.caPrivateKey, result.caExpiresOn));
|
|
95
|
+
ux.stdout('');
|
|
96
|
+
ux.stdout(clientCertificateNotice({
|
|
97
|
+
file: result.files.clientCertificate,
|
|
98
|
+
hostname: cert.mtlsAssociatedCodeUploadHostname,
|
|
99
|
+
passphrase: result.clientPassphrase,
|
|
100
|
+
}));
|
|
101
|
+
if (output.configUpdated) {
|
|
102
|
+
ux.stdout('');
|
|
103
|
+
ux.stdout(t('commands.ecdn.mtls.setup.configNote', 'dw.json now contains the .p12 passphrase; make sure dw.json is not committed to your repository.'));
|
|
104
|
+
}
|
|
105
|
+
return output;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns `false` if no generated files exist, `true` if the user agreed to overwrite, or `undefined` to cancel.
|
|
109
|
+
*/
|
|
110
|
+
async confirmOverwrite(outDir) {
|
|
111
|
+
const existing = ['ca.pem', 'ca.key'].map((f) => path.resolve(outDir, f)).filter((f) => fs.existsSync(f));
|
|
112
|
+
if (existing.length === 0) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const overwrite = await confirm({
|
|
116
|
+
message: t('commands.ecdn.mtls.setup.overwrite', 'Overwrite existing files ({{files}})?', {
|
|
117
|
+
files: existing.join(', '),
|
|
118
|
+
}),
|
|
119
|
+
default: false,
|
|
120
|
+
});
|
|
121
|
+
return overwrite ? true : undefined;
|
|
122
|
+
}
|
|
123
|
+
async listExisting() {
|
|
124
|
+
const { data, error } = await this.getCdnZonesClient().GET('/organizations/{organizationId}/mtls/code-upload-certificates', { params: { path: { organizationId: this.getOrganizationId() } } });
|
|
125
|
+
if (error) {
|
|
126
|
+
this.logger.debug({ error }, 'Failed to list existing mTLS certificates');
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
return data?.data ?? [];
|
|
130
|
+
}
|
|
131
|
+
async maybeUpdateConfig(files, passphrase) {
|
|
132
|
+
// Prefer the dw.json the configuration was actually loaded from (it may be in a parent directory).
|
|
133
|
+
const loadedFrom = this.resolvedConfig.sources.find((s) => s.name === 'DwJsonSource' && s.location)?.location;
|
|
134
|
+
const dwJsonPath = path.resolve(loadedFrom ?? this.flags.config ?? path.join(this.flags['project-directory'] ?? process.cwd(), 'dw.json'));
|
|
135
|
+
if (!fs.existsSync(dwJsonPath)) {
|
|
136
|
+
ux.stdout(t('commands.ecdn.mtls.setup.noConfig', 'No dw.json found; to upload from this machine, configure "certificate" and "certificate-passphrase" manually.'));
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const update = await confirm({
|
|
140
|
+
message: t('commands.ecdn.mtls.setup.updateConfig', 'Also use this certificate for code upload from this machine (updates {{path}})?', { path: dwJsonPath }),
|
|
141
|
+
default: false,
|
|
142
|
+
});
|
|
143
|
+
if (!update) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const result = await updateInstanceConfig({
|
|
148
|
+
certificate: files.clientCertificate,
|
|
149
|
+
certificatePassphrase: passphrase,
|
|
150
|
+
}, { path: dwJsonPath, instance: this.flags.instance });
|
|
151
|
+
ux.stdout(t('commands.ecdn.mtls.setup.configUpdated', 'Updated instance "{{name}}" in {{path}}', {
|
|
152
|
+
name: result.name ?? 'root',
|
|
153
|
+
path: result.path,
|
|
154
|
+
}));
|
|
155
|
+
return result.path;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
this.warn(t('commands.ecdn.mtls.setup.configUpdateFailed', 'Could not update dw.json: {{message}}', {
|
|
159
|
+
message: error instanceof Error ? error.message : String(error),
|
|
160
|
+
}));
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=setup.js.map
|