feat: accept private key from client #381
This commit is contained in:
parent
b511ce5eae
commit
829b5cdd75
9 changed files with 24 additions and 29 deletions
|
@ -84,7 +84,7 @@ Renamed and expanded options:
|
||||||
## Detailed Changes
|
## Detailed Changes
|
||||||
|
|
||||||
### 1. Authentication Options
|
### 1. Authentication Options
|
||||||
- Added support for SSH private key authentication via `user.privatekey`
|
- Added support for SSH private key authentication via `user.privateKey`
|
||||||
- Removed `user.overridebasic` option
|
- Removed `user.overridebasic` option
|
||||||
- Added keyboard-interactive authentication controls
|
- Added keyboard-interactive authentication controls
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ These settings are now managed client-side.
|
||||||
"user": {
|
"user": {
|
||||||
"name": null,
|
"name": null,
|
||||||
"password": null,
|
"password": null,
|
||||||
"privatekey": null
|
"privateKey": null
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"host": null,
|
"host": null,
|
||||||
|
|
|
@ -115,7 +115,7 @@ Edit `config.json` to customize the following options:
|
||||||
- `user.name` - _string_ - Default SSH username (default: `null`)
|
- `user.name` - _string_ - Default SSH username (default: `null`)
|
||||||
- `user.password` - _string_ - Default SSH password (default: `null`)
|
- `user.password` - _string_ - Default SSH password (default: `null`)
|
||||||
- `ssh.host` - _string_ - Default SSH host (default: `null`)
|
- `ssh.host` - _string_ - Default SSH host (default: `null`)
|
||||||
- `user.privatekey` - _string_ - Default SSH private key (default: `null`)
|
- `user.privateKey` - _string_ - Default SSH private key (default: `null`)
|
||||||
- `ssh.port` - _integer_ - Default SSH port (default: `22`)
|
- `ssh.port` - _integer_ - Default SSH port (default: `22`)
|
||||||
- `ssh.term` - _string_ - Terminal emulation (default: `"xterm-color"`)
|
- `ssh.term` - _string_ - Terminal emulation (default: `"xterm-color"`)
|
||||||
- `ssh.readyTimeout` - _integer_ - SSH handshake timeout in ms (default: `20000`)
|
- `ssh.readyTimeout` - _integer_ - SSH handshake timeout in ms (default: `20000`)
|
||||||
|
@ -206,7 +206,7 @@ Private key authentication can only be configured through the `config.json` file
|
||||||
{
|
{
|
||||||
"user": {
|
"user": {
|
||||||
"name": "myuser",
|
"name": "myuser",
|
||||||
"privatekey": "-----BEGIN RSA PRIVATE KEY-----\nYour-Private-Key-Here\n-----END RSA PRIVATE KEY-----",
|
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nYour-Private-Key-Here\n-----END RSA PRIVATE KEY-----",
|
||||||
"password": "optional-fallback-password"
|
"password": "optional-fallback-password"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,7 @@ This command:
|
||||||
{
|
{
|
||||||
"user": {
|
"user": {
|
||||||
"name": "myuser",
|
"name": "myuser",
|
||||||
"privatekey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpA...[rest of key]...Yh5Q==\n-----END RSA PRIVATE KEY-----",
|
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpA...[rest of key]...Yh5Q==\n-----END RSA PRIVATE KEY-----",
|
||||||
"password": "fallback-password"
|
"password": "fallback-password"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,8 @@ const configSchema = {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
name: { type: ["string", "null"] },
|
name: { type: ["string", "null"] },
|
||||||
password: { type: ["string", "null"] }
|
password: { type: ["string", "null"] },
|
||||||
|
privateKey: { type: ["string", "null"] }
|
||||||
},
|
},
|
||||||
required: ["name", "password"]
|
required: ["name", "password"]
|
||||||
},
|
},
|
||||||
|
@ -39,7 +40,6 @@ const configSchema = {
|
||||||
readyTimeout: { type: "integer" },
|
readyTimeout: { type: "integer" },
|
||||||
keepaliveInterval: { type: "integer" },
|
keepaliveInterval: { type: "integer" },
|
||||||
keepaliveCountMax: { type: "integer" },
|
keepaliveCountMax: { type: "integer" },
|
||||||
disableInteractiveAuth: { type: "boolean" },
|
|
||||||
algorithms: {
|
algorithms: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
|
|
|
@ -32,14 +32,14 @@ function createAuthMiddleware(config) {
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
// Check if username and either password or private key is configured
|
// Check if username and either password or private key is configured
|
||||||
if (config.user.name && (config.user.password || config.user.privatekey)) {
|
if (config.user.name && (config.user.password || config.user.privateKey)) {
|
||||||
req.session.sshCredentials = {
|
req.session.sshCredentials = {
|
||||||
username: config.user.name
|
username: config.user.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add credentials based on what's available
|
// Add credentials based on what's available
|
||||||
if (config.user.privatekey) {
|
if (config.user.privateKey) {
|
||||||
req.session.sshCredentials.privatekey = config.user.privatekey
|
req.session.sshCredentials.privateKey = config.user.privateKey
|
||||||
}
|
}
|
||||||
if (config.user.password) {
|
if (config.user.password) {
|
||||||
req.session.sshCredentials.password = config.user.password
|
req.session.sshCredentials.password = config.user.password
|
||||||
|
|
|
@ -25,7 +25,7 @@ class WebSSH2Socket extends EventEmitter {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
username: null,
|
username: null,
|
||||||
password: null,
|
password: null,
|
||||||
privatekey: null,
|
privateKey: null,
|
||||||
keyPassword: null,
|
keyPassword: null,
|
||||||
host: null,
|
host: null,
|
||||||
port: null,
|
port: null,
|
||||||
|
@ -117,11 +117,6 @@ class WebSSH2Socket extends EventEmitter {
|
||||||
? creds.term
|
? creds.term
|
||||||
: this.config.ssh.term
|
: this.config.ssh.term
|
||||||
|
|
||||||
// Map the client's privateKey field to our internal privatekey field if present
|
|
||||||
if (creds.privateKey) {
|
|
||||||
creds.privatekey = creds.privateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
this.initializeConnection(creds)
|
this.initializeConnection(creds)
|
||||||
} else {
|
} else {
|
||||||
debug(`handleAuthenticate: ${this.socket.id}, CREDENTIALS INVALID`)
|
debug(`handleAuthenticate: ${this.socket.id}, CREDENTIALS INVALID`)
|
||||||
|
@ -139,8 +134,8 @@ class WebSSH2Socket extends EventEmitter {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add private key from config if available and not provided in creds
|
// Add private key from config if available and not provided in creds
|
||||||
if (this.config.user.privatekey && !creds.privatekey) {
|
if (this.config.user.privateKey && !creds.privateKey) {
|
||||||
creds.privatekey = this.config.user.privatekey
|
creds.privateKey = this.config.user.privateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ssh
|
this.ssh
|
||||||
|
@ -150,7 +145,7 @@ class WebSSH2Socket extends EventEmitter {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
username: creds.username,
|
username: creds.username,
|
||||||
password: creds.password,
|
password: creds.password,
|
||||||
privatekey: creds.privatekey,
|
privateKey: creds.privateKey,
|
||||||
keyPassword: creds.keyPassword,
|
keyPassword: creds.keyPassword,
|
||||||
host: creds.host,
|
host: creds.host,
|
||||||
port: creds.port
|
port: creds.port
|
||||||
|
|
|
@ -160,9 +160,9 @@ class SSHConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try private key first if available and useKey is true
|
// Try private key first if available and useKey is true
|
||||||
if (useKey && (creds.privatekey || this.config.user.privatekey)) {
|
if (useKey && (creds.privateKey || this.config.user.privateKey)) {
|
||||||
debug("Using private key authentication")
|
debug("Using private key authentication")
|
||||||
const privateKey = creds.privatekey || this.config.user.privatekey
|
const privateKey = creds.privateKey || this.config.user.privateKey
|
||||||
if (!this.validatePrivateKey(privateKey)) {
|
if (!this.validatePrivateKey(privateKey)) {
|
||||||
throw new SSHConnectionError("Invalid private key format")
|
throw new SSHConnectionError("Invalid private key format")
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ function getValidatedPort(portInput) {
|
||||||
* - port (number)
|
* - port (number)
|
||||||
* AND either:
|
* AND either:
|
||||||
* - password (string) OR
|
* - password (string) OR
|
||||||
* - privatekey/privateKey (string)
|
* - privateKey/privateKey (string)
|
||||||
*
|
*
|
||||||
* @param {Object} creds - The credentials object.
|
* @param {Object} creds - The credentials object.
|
||||||
* @returns {boolean} - Returns true if the credentials are valid, otherwise false.
|
* @returns {boolean} - Returns true if the credentials are valid, otherwise false.
|
||||||
|
@ -104,10 +104,10 @@ function isValidCredentials(creds) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must have either password or privatekey/privateKey
|
// Must have either password or privateKey/privateKey
|
||||||
const hasPassword = typeof creds.password === "string"
|
const hasPassword = typeof creds.password === "string"
|
||||||
const hasPrivateKey =
|
const hasPrivateKey =
|
||||||
typeof creds.privatekey === "string" || typeof creds.privateKey === "string"
|
typeof creds.privateKey === "string" || typeof creds.privateKey === "string"
|
||||||
|
|
||||||
return hasPassword || hasPrivateKey
|
return hasPassword || hasPrivateKey
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ describe("SSHConnection", () => {
|
||||||
user: {
|
user: {
|
||||||
name: null,
|
name: null,
|
||||||
password: null,
|
password: null,
|
||||||
privatekey: null
|
privateKey: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ MIIEpTestKeyContentHere
|
||||||
port: 22,
|
port: 22,
|
||||||
username: "user",
|
username: "user",
|
||||||
password: "pass",
|
password: "pass",
|
||||||
privatekey: validPrivateKey
|
privateKey: validPrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
return sshConnection.connect(mockCreds).then(() => {
|
return sshConnection.connect(mockCreds).then(() => {
|
||||||
|
@ -168,7 +168,7 @@ MIIEpTestKeyContentHere
|
||||||
port: 22,
|
port: 22,
|
||||||
username: "user",
|
username: "user",
|
||||||
password: "pass",
|
password: "pass",
|
||||||
privatekey: validPrivateKey
|
privateKey: validPrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
let authAttempts = 0
|
let authAttempts = 0
|
||||||
|
@ -205,7 +205,7 @@ MIIEpTestKeyContentHere
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
port: 22,
|
port: 22,
|
||||||
username: "user",
|
username: "user",
|
||||||
privatekey: "invalid-key-format"
|
privateKey: "invalid-key-format"
|
||||||
}
|
}
|
||||||
|
|
||||||
return sshConnection.connect(mockCreds).catch((error) => {
|
return sshConnection.connect(mockCreds).catch((error) => {
|
||||||
|
|
|
@ -152,7 +152,7 @@ describe("utils", () => {
|
||||||
user: {
|
user: {
|
||||||
name: null,
|
name: null,
|
||||||
password: null,
|
password: null,
|
||||||
privatekey: null
|
privateKey: null
|
||||||
},
|
},
|
||||||
ssh: {
|
ssh: {
|
||||||
host: null,
|
host: null,
|
||||||
|
|
Loading…
Reference in a new issue