cisco-ise 1.0.0
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/EXAMPLES.md +46 -0
- package/RADIUS.md +19 -0
- package/README.md +222 -0
- package/bin/cisco-ise.js +14 -0
- package/cli/commands/auth-profile.js +36 -0
- package/cli/commands/config.js +140 -0
- package/cli/commands/deployment.js +49 -0
- package/cli/commands/endpoint.js +167 -0
- package/cli/commands/guest.js +220 -0
- package/cli/commands/identity-group.js +24 -0
- package/cli/commands/internal-user.js +162 -0
- package/cli/commands/network-device.js +167 -0
- package/cli/commands/radius.js +326 -0
- package/cli/commands/session.js +123 -0
- package/cli/commands/tacacs.js +125 -0
- package/cli/commands/trustsec.js +37 -0
- package/cli/formatters/csv.js +10 -0
- package/cli/formatters/json.js +5 -0
- package/cli/formatters/table.js +29 -0
- package/cli/formatters/toon.js +6 -0
- package/cli/index.js +44 -0
- package/cli/utils/api.js +297 -0
- package/cli/utils/audit.js +30 -0
- package/cli/utils/config.js +125 -0
- package/cli/utils/confirm.js +34 -0
- package/cli/utils/connection.js +47 -0
- package/cli/utils/failure-reasons.js +2086 -0
- package/cli/utils/mac.js +18 -0
- package/cli/utils/output.js +42 -0
- package/cli/utils/spinner.js +19 -0
- package/cli/utils/time.js +21 -0
- package/cli/utils/wordlist.js +9 -0
- package/docs/PHASES.md +38 -0
- package/package.json +45 -0
- package/skills/cisco-ise-cli/SKILL.md +346 -0
- package/test/cli/api.test.js +67 -0
- package/test/cli/audit.test.js +31 -0
- package/test/cli/config.test.js +60 -0
- package/test/cli/confirm.test.js +34 -0
- package/test/cli/connection.test.js +54 -0
- package/test/cli/formatters.test.js +41 -0
- package/test/cli/mac.test.js +37 -0
- package/test/cli/time.test.js +30 -0
- package/test/integration/ise.test.js +425 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const os = require("node:os");
|
|
4
|
+
const { execSync } = require("node:child_process");
|
|
5
|
+
|
|
6
|
+
const SS_PLACEHOLDER_RE = /<ss:(\d+):(\w+)>/g;
|
|
7
|
+
|
|
8
|
+
function getConfigDir() {
|
|
9
|
+
return process.env.CISCO_ISE_CONFIG_DIR || path.join(os.homedir(), ".cisco-ise");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getConfigPath() {
|
|
13
|
+
return path.join(getConfigDir(), "config.json");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function loadConfig() {
|
|
17
|
+
const configPath = getConfigPath();
|
|
18
|
+
if (!fs.existsSync(configPath)) {
|
|
19
|
+
return { activeCluster: null, clusters: {} };
|
|
20
|
+
}
|
|
21
|
+
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function saveConfig(config) {
|
|
25
|
+
const dir = getConfigDir();
|
|
26
|
+
if (!fs.existsSync(dir)) {
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
28
|
+
}
|
|
29
|
+
fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function addCluster(name, opts) {
|
|
33
|
+
const config = loadConfig();
|
|
34
|
+
config.clusters[name] = {
|
|
35
|
+
host: opts.host,
|
|
36
|
+
username: opts.username,
|
|
37
|
+
password: opts.password,
|
|
38
|
+
};
|
|
39
|
+
if (opts.ppan) config.clusters[name].ppan = opts.ppan;
|
|
40
|
+
if (opts.pmnt) config.clusters[name].pmnt = opts.pmnt;
|
|
41
|
+
if (opts.sponsorUser) config.clusters[name].sponsorUser = opts.sponsorUser;
|
|
42
|
+
if (opts.sponsorPassword) config.clusters[name].sponsorPassword = opts.sponsorPassword;
|
|
43
|
+
if (opts.insecure) config.clusters[name].insecure = true;
|
|
44
|
+
if (opts.readOnly) config.clusters[name].readOnly = true;
|
|
45
|
+
if (!config.activeCluster) config.activeCluster = name;
|
|
46
|
+
saveConfig(config);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function useCluster(name) {
|
|
50
|
+
const config = loadConfig();
|
|
51
|
+
if (!config.clusters[name]) {
|
|
52
|
+
throw new Error(`Cluster "${name}" not found. Run "cisco-ise config list" to see available clusters.`);
|
|
53
|
+
}
|
|
54
|
+
config.activeCluster = name;
|
|
55
|
+
saveConfig(config);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function removeCluster(name) {
|
|
59
|
+
const config = loadConfig();
|
|
60
|
+
if (!config.clusters[name]) throw new Error(`Cluster "${name}" not found.`);
|
|
61
|
+
delete config.clusters[name];
|
|
62
|
+
if (config.activeCluster === name) {
|
|
63
|
+
const remaining = Object.keys(config.clusters);
|
|
64
|
+
config.activeCluster = remaining.length > 0 ? remaining[0] : null;
|
|
65
|
+
}
|
|
66
|
+
saveConfig(config);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getActiveCluster(clusterName) {
|
|
70
|
+
const config = loadConfig();
|
|
71
|
+
const name = clusterName || config.activeCluster;
|
|
72
|
+
if (!name || !config.clusters[name]) return null;
|
|
73
|
+
return { name, ...config.clusters[name] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function listClusters() {
|
|
77
|
+
const config = loadConfig();
|
|
78
|
+
return config.clusters;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function updateCluster(name, updates) {
|
|
82
|
+
const config = loadConfig();
|
|
83
|
+
if (!config.clusters[name]) throw new Error(`Cluster "${name}" not found.`);
|
|
84
|
+
Object.assign(config.clusters[name], updates);
|
|
85
|
+
saveConfig(config);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function maskPassword(password) {
|
|
89
|
+
if (!password) return "";
|
|
90
|
+
if (SS_PLACEHOLDER_RE.test(password)) { SS_PLACEHOLDER_RE.lastIndex = 0; return password; }
|
|
91
|
+
return "*".repeat(password.length);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function resolveSecrets(value) {
|
|
95
|
+
if (typeof value !== "string") return value;
|
|
96
|
+
SS_PLACEHOLDER_RE.lastIndex = 0;
|
|
97
|
+
if (!SS_PLACEHOLDER_RE.test(value)) return value;
|
|
98
|
+
SS_PLACEHOLDER_RE.lastIndex = 0;
|
|
99
|
+
return value.replace(SS_PLACEHOLDER_RE, (match, id, field) => {
|
|
100
|
+
try {
|
|
101
|
+
const output = execSync(`ss-cli get ${id} --format json`, { encoding: "utf-8", timeout: 10000 });
|
|
102
|
+
const secret = JSON.parse(output);
|
|
103
|
+
if (secret[field] !== undefined) return secret[field];
|
|
104
|
+
if (Array.isArray(secret.items)) {
|
|
105
|
+
const item = secret.items.find((i) => i.fieldName === field || i.slug === field);
|
|
106
|
+
if (item) return item.itemValue;
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Field "${field}" not found in secret ${id}`);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (err.message.includes("ENOENT") || err.message.includes("not found")) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"Config contains Secret Server references (<ss:...>) but ss-cli is not available. " +
|
|
113
|
+
"Install with: npm install -g @sieteunoseis/ss-cli"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
getConfigDir, getConfigPath, loadConfig, saveConfig,
|
|
123
|
+
addCluster, useCluster, removeCluster, getActiveCluster,
|
|
124
|
+
listClusters, updateCluster, maskPassword, resolveSecrets,
|
|
125
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const readline = require("readline");
|
|
2
|
+
const { getRandomWord } = require("./wordlist.js");
|
|
3
|
+
|
|
4
|
+
function checkWriteAllowed(clusterConfig, globalOpts = {}) {
|
|
5
|
+
const readOnly = clusterConfig?.readOnly || globalOpts.readOnly;
|
|
6
|
+
if (!readOnly) return Promise.resolve(true);
|
|
7
|
+
|
|
8
|
+
if (!process.stdin.isTTY) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"This cluster is configured as read-only. " +
|
|
11
|
+
"Interactive TTY required for write confirmation. " +
|
|
12
|
+
"Change config with: cisco-ise config update <name> --no-read-only"
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const word = getRandomWord();
|
|
17
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
18
|
+
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
rl.question(
|
|
21
|
+
`\n⚠ This cluster is configured as read-only.\nTo proceed, type "${word}" to confirm: `,
|
|
22
|
+
(answer) => {
|
|
23
|
+
rl.close();
|
|
24
|
+
if (answer.trim().toLowerCase() === word.toLowerCase()) {
|
|
25
|
+
resolve({ confirmed: true, word });
|
|
26
|
+
} else {
|
|
27
|
+
reject(new Error("Confirmation failed. Write operation cancelled."));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { checkWriteAllowed };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const config = require("./config.js");
|
|
2
|
+
|
|
3
|
+
function resolveConnection(opts) {
|
|
4
|
+
let cluster;
|
|
5
|
+
try {
|
|
6
|
+
cluster = opts.cluster
|
|
7
|
+
? config.getActiveCluster(opts.cluster)
|
|
8
|
+
: config.getActiveCluster();
|
|
9
|
+
} catch {
|
|
10
|
+
cluster = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!cluster && !opts.host && !process.env.CISCO_ISE_HOST) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"No cluster configured. Run: cisco-ise config add <name> --host <host> --username <user> --password <pass>"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const resolved = {
|
|
20
|
+
host: opts.host || process.env.CISCO_ISE_HOST || cluster?.host,
|
|
21
|
+
username: opts.username || process.env.CISCO_ISE_USERNAME || cluster?.username,
|
|
22
|
+
password: opts.password || process.env.CISCO_ISE_PASSWORD || cluster?.password,
|
|
23
|
+
ppan: opts.ppan || process.env.CISCO_ISE_PPAN || cluster?.ppan || undefined,
|
|
24
|
+
pmnt: opts.pmnt || process.env.CISCO_ISE_PMNT || cluster?.pmnt || undefined,
|
|
25
|
+
sponsorUser: opts.sponsorUser || process.env.CISCO_ISE_SPONSOR_USER || cluster?.sponsorUser || undefined,
|
|
26
|
+
sponsorPassword: opts.sponsorPassword || process.env.CISCO_ISE_SPONSOR_PASSWORD || cluster?.sponsorPassword || undefined,
|
|
27
|
+
insecure: opts.insecure ?? cluster?.insecure ?? false,
|
|
28
|
+
readOnly: opts.readOnly ?? cluster?.readOnly ?? false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (!resolved.host) throw new Error("Missing --host. Provide via flag, env var CISCO_ISE_HOST, or config.");
|
|
32
|
+
if (!resolved.username) throw new Error("Missing --username.");
|
|
33
|
+
if (!resolved.password) throw new Error("Missing --password.");
|
|
34
|
+
|
|
35
|
+
for (const key of ["host", "username", "password", "ppan", "pmnt", "sponsorUser", "sponsorPassword"]) {
|
|
36
|
+
if (resolved[key]) resolved[key] = config.resolveSecrets(resolved[key]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Set TLS override if insecure (covers config-file sourced flag)
|
|
40
|
+
if (resolved.insecure) {
|
|
41
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return resolved;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { resolveConnection };
|