Compare commits

..

10 commits
main ... bigip

Author SHA1 Message Date
Bill Church
9537f4da67
fix: read-config-ng references 2024-07-11 11:32:17 +00:00
Bill Church
ea12cc8b7e
fix: version comment in client.html 2024-07-10 12:03:10 -04:00
Bill Church
cfa097bd0e
chore: swap read-config git to read-config-ng 2024-07-10 11:00:53 -04:00
Bill Church
170047517b
chore: build 0.2.12 web assets 2024-07-10 13:50:36 +00:00
Bill Church
cd64cc0637
chore: update webpack 2024-07-10 13:50:18 +00:00
Bill Church
533f719cca
chore: dev build testing 2024-07-10 13:09:24 +00:00
Bill Church
b8782c565a
chore: release 0.2.12 2024-07-10 12:18:21 +00:00
Bill Church
0dda8d56d9
build: release 0.2.12 2024-07-10 12:13:53 +00:00
Bill Church
24c94aed98
chore: update dev environment 2024-07-10 12:12:33 +00:00
Bill Church
7223f2cd8f chore: big-ip specific release v0.2.11 2021-05-12 13:59:43 -04:00
76 changed files with 1961 additions and 13929 deletions

21
.bithoundrc Normal file
View file

@ -0,0 +1,21 @@
{
"critics": {
"lint": {
"engine": "standard"
},
"wc": {
"limit": 5000
}
},
"dependencies": {
"mute": [
"read-config",
"socket.io",
"standard",
"bithound"
]
},
"ignore": [
"public/webssh2.bundle.js",
]
}

36
.codeclimate.yml Normal file
View file

@ -0,0 +1,36 @@
---
engines:
csslint:
enabled: true
exclude_paths:
- "client/public/*"
duplication:
exclude_paths:
- "client/public/*"
- "workspace/*"
enabled: true
config:
languages:
- ruby
- javascript
- python
- php
eslint:
enabled: true
fixme:
enabled: true
ratings:
paths:
- "**.css"
- "**.inc"
- "**.js"
- "**.jsx"
- "**.module"
- "**.php"
- "**.py"
- "**.rb"
exclude_paths:
- "node_modules/"
- "client/public/*"
- "workspace/*"

View file

@ -1,37 +1,42 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{ {
"name": "Node.js & TypeScript", "name": "Node.js & TypeScript",
"image": "mcr.microsoft.com/devcontainers/base:jammy", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:jammy",
// mount the ssh public identity file for the this project
// I limit to just what I need and not the whole ~/.ssh folder
"mounts": [
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh/personal_id_rsa.pub,target=/home/vscode/.hostssh/id_rsa.pub,readonly,type=bind,consistency=cached"
],
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers-contrib/features/node-asdf": {},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
},
"mounts": [ // Use 'forwardPorts' to make a list of ports inside the container available locally.
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh/personal_id_rsa.pub,target=/home/vscode/.hostssh/id_rsa.pub,readonly,type=bind,consistency=cached" // "forwardPorts": [],
],
"features": {
"ghcr.io/devcontainers-contrib/features/node-asdf:0": {},
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-vscode-remote.remote-containers",
"dbaeumer.vscode-eslint",
"GitHub.copilot",
"GitHub.copilot-chat",
"esbenp.prettier-vscode",
"rvest.vs-code-prettier-eslint",
"bierner.markdown-mermaid",
"stylelint.vscode-stylelint"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally. "customizations": {
// "forwardPorts": [], "vscode": {
"extensions": [
"mechatroner.rainbow-csv",
"ms-vscode-remote.remote-containers",
"dbaeumer.vscode-eslint",
"GitHub.copilot",
"GitHub.copilot-chat",
"esbenp.prettier-vscode",
"rvest.vs-code-prettier-eslint",
"bierner.markdown-mermaid",
"stylelint.vscode-stylelint"
]
}
},
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "/bin/bash ./.devcontainer/scripts/tools.sh >> ~/post-create-tools.log", "postCreateCommand": "/bin/bash ./.devcontainer/scripts/tools.sh >> ~/post-create-tools.log"
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "vscode" // "remoteUser": "root"
} }

View file

@ -9,4 +9,10 @@ Host github.com
IdentityFile ~/.hostssh/id_rsa.pub IdentityFile ~/.hostssh/id_rsa.pub
EOF EOF
sudo chown -R vscode:vscode ~/.ssh sudo chown -R vscode:vscode ~/.ssh
# Install Node.js 6.9.1
asdf install nodejs 6.9.1
asdf local nodejs 6.9.1
git config --global --add safe.directory /workspaces/webssh2

View file

@ -1,9 +0,0 @@
.git
.cache
app/node_modules
app/client/src
app/scripts
app/.*
app/*.sample
app/client/tsconfig.json
app/CHANGELOG.md

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
**/*{.,-}min.js

277
.eslintrc.yml Normal file
View file

@ -0,0 +1,277 @@
---
parserOptions:
sourceType: module
ecmaFeatures:
jsx: true
env:
amd: true
browser: true
es6: true
jquery: true
node: true
# http://eslint.org/docs/rules/
rules:
# Possible Errors
no-await-in-loop: off
no-cond-assign: error
no-console: off
no-constant-condition: error
no-control-regex: error
no-debugger: error
no-dupe-args: error
no-dupe-keys: error
no-duplicate-case: error
no-empty-character-class: error
no-empty: error
no-ex-assign: error
no-extra-boolean-cast: error
no-extra-parens: off
no-extra-semi: error
no-func-assign: error
no-inner-declarations:
- error
- functions
no-invalid-regexp: error
no-irregular-whitespace: error
no-negated-in-lhs: error
no-obj-calls: error
no-prototype-builtins: off
no-regex-spaces: error
no-sparse-arrays: error
no-template-curly-in-string: off
no-unexpected-multiline: error
no-unreachable: error
no-unsafe-finally: off
no-unsafe-negation: off
use-isnan: error
valid-jsdoc: off
valid-typeof: error
# Best Practices
accessor-pairs: error
array-callback-return: off
block-scoped-var: off
class-methods-use-this: off
complexity:
- error
- 6
consistent-return: off
curly: off
default-case: off
dot-location: off
dot-notation: off
eqeqeq: error
guard-for-in: error
no-alert: error
no-caller: error
no-case-declarations: error
no-div-regex: error
no-else-return: off
no-empty-function: off
no-empty-pattern: error
no-eq-null: error
no-eval: error
no-extend-native: error
no-extra-bind: error
no-extra-label: off
no-fallthrough: error
no-floating-decimal: off
no-global-assign: off
no-implicit-coercion: off
no-implied-eval: error
no-invalid-this: off
no-iterator: error
no-labels:
- error
- allowLoop: true
allowSwitch: true
no-lone-blocks: error
no-loop-func: error
no-magic-number: off
no-multi-spaces: off
no-multi-str: off
no-native-reassign: error
no-new-func: error
no-new-wrappers: error
no-new: error
no-octal-escape: error
no-octal: error
no-param-reassign: off
no-proto: error
no-redeclare: error
no-restricted-properties: off
no-return-assign: error
no-return-await: off
no-script-url: error
no-self-assign: off
no-self-compare: error
no-sequences: off
no-throw-literal: off
no-unmodified-loop-condition: off
no-unused-expressions: error
no-unused-labels: off
no-useless-call: error
no-useless-concat: error
no-useless-escape: off
no-useless-return: off
no-void: error
no-warning-comments: off
no-with: error
prefer-promise-reject-errors: off
radix: error
require-await: off
vars-on-top: off
wrap-iife: error
yoda: off
# Strict
strict: off
# Variables
init-declarations: off
no-catch-shadow: error
no-delete-var: error
no-label-var: error
no-restricted-globals: off
no-shadow-restricted-names: error
no-shadow: off
no-undef-init: error
no-undef: off
no-undefined: off
no-unused-vars: off
no-use-before-define: off
# Node.js and CommonJS
callback-return: error
global-require: error
handle-callback-err: error
no-mixed-requires: off
no-new-require: off
no-path-concat: error
no-process-env: off
no-process-exit: error
no-restricted-modules: off
no-sync: off
# Stylistic Issues
array-bracket-spacing: off
block-spacing: off
brace-style: off
camelcase: off
capitalized-comments: off
comma-dangle:
- error
- never
comma-spacing: off
comma-style: off
computed-property-spacing: off
consistent-this: off
eol-last: off
func-call-spacing: off
func-name-matching: off
func-names: off
func-style: off
id-length: off
id-match: off
indent: off
jsx-quotes: off
key-spacing: off
keyword-spacing: off
line-comment-position: off
linebreak-style: off
lines-around-comment: off
lines-around-directive: off
max-depth: off
max-len: off
max-nested-callbacks: off
max-params: off
max-statements-per-line: off
max-statements:
- error
- 30
multiline-ternary: off
new-cap: off
new-parens: off
newline-after-var: off
newline-before-return: off
newline-per-chained-call: off
no-array-constructor: off
no-bitwise: off
no-continue: off
no-inline-comments: off
no-lonely-if: off
no-mixed-operators: off
no-mixed-spaces-and-tabs: off
no-multi-assign: off
no-multiple-empty-lines: off
no-negated-condition: off
no-nested-ternary: off
no-new-object: off
no-plusplus: off
no-restricted-syntax: off
no-spaced-func: off
no-tabs: off
no-ternary: off
no-trailing-spaces: off
no-underscore-dangle: off
no-unneeded-ternary: off
object-curly-newline: off
object-curly-spacing: off
object-property-newline: off
one-var-declaration-per-line: off
one-var: off
operator-assignment: off
operator-linebreak: off
padded-blocks: off
quote-props: off
quotes: off
require-jsdoc: off
semi-spacing: off
semi: off
sort-keys: off
sort-vars: off
space-before-blocks: off
space-before-function-paren: off
space-in-parens: off
space-infix-ops: off
space-unary-ops: off
spaced-comment: off
template-tag-spacing: off
unicode-bom: off
wrap-regex: off
# ECMAScript 6
arrow-body-style: off
arrow-parens: off
arrow-spacing: off
constructor-super: off
generator-star-spacing: off
no-class-assign: off
no-confusing-arrow: off
no-const-assign: off
no-dupe-class-members: off
no-duplicate-imports: off
no-new-symbol: off
no-restricted-imports: off
no-this-before-super: off
no-useless-computed-key: off
no-useless-constructor: off
no-useless-rename: off
no-var: off
object-shorthand: off
prefer-arrow-callback: off
prefer-const: off
prefer-destructuring: off
prefer-numeric-literals: off
prefer-rest-params: off
prefer-reflect: off
prefer-spread: off
prefer-template: off
require-yield: off
rest-spread-spacing: off
sort-imports: off
symbol-description: off
template-curly-spacing: off
yield-star-spacing: off

View file

@ -1,75 +0,0 @@
name: Bug Report
description: File a bug report
title: "[Bug]: "
labels: ["bug", "triage"]
assignees:
- billchurch
body:
- type: markdown
attributes:
value: |
Depending on the type of issue, please include the follwing information:
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: input
id: node_ver
attributes:
label: Node Version
description: version of Node this problem occurs on
placeholder: npm -v
validations:
required: true
- type: input
id: npm_ver
attributes:
label: NPM Version
description: version of NPM this problem occurs on
placeholder: npm -v
validations:
required: true
- type: input
id: server_ver
attributes:
label: Server OS Version
description: Server OS Version / Distribution / Processor Architecture
placeholder: uname -a;cat /etc/os-release
validations:
required: true
- type: input
id: webssh2_ver
attributes:
label: WebSSH2 release version
description: Version of WebSSH you are using
placeholder: grep version app/package.json
validations:
required: true
- type: input
id: sshhost_ver
attributes:
label: OS and Version of SSH server
description: OS and Version of SSH server connecting to
placeholder: 'on target server run: uname -a;sshd -v'
validations:
required: false
- type: input
id: browser_ver
attributes:
label: Browser Version
description: Information from brwoser's About... or a screenshot of the about screen.
placeholder:
validations:
required: false
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell

View file

@ -1,10 +0,0 @@
---
name: Question
about: General how-to questions
title: ''
labels: ''
assignees: ''
---

View file

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,65 +0,0 @@
name: Manually Release Previous Tag
on:
workflow_dispatch:
inputs:
tag:
description: 'Repo Branch/Tag'
default: 'main'
type: 'string'
required: true
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v3
with:
ref: ${{ inputs.tag }}
- name: Prepare
id: prep
run: |
DOCKER_IMAGE=${{ secrets.DOCKER_USERNAME }}/${GITHUB_REPOSITORY#*/}
VERSION=${{ inputs.tag }}
VERSION="${VERSION//v}"
TAGS="${DOCKER_IMAGE}:${VERSION},${DOCKER_IMAGE}"
# If the VERSION looks like a version number, assume that
# this is the most recent version of the image and also
# tag it 'latest'.
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
TAGS="$TAGS,${DOCKER_IMAGE}"
fi
# Set output parameters.
echo ::set-output name=tags::${TAGS}
echo ::set-output name=docker_image::${DOCKER_IMAGE}
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build
uses: docker/build-push-action@v2
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/ppc64le
push: true
tags: ${{ steps.prep.outputs.tags }}

View file

@ -1,69 +0,0 @@
---
name: 'Build Docker On Tag'
on:
push:
branches:
- bigip-server
tags:
- 'v[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
workflow_dispatch: # Allows manual triggering from the GitHub UI
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v3
- name: Prepare
id: prep
run: |
DOCKER_IMAGE=${{ secrets.DOCKER_USERNAME }}/${GITHUB_REPOSITORY#*/}
# If this is a git tag, use the tag name as a docker tag
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
TAGS="${DOCKER_IMAGE}:${VERSION}"
fi
# If this is a git branch, use the branch name as a docker tag
if [[ $GITHUB_REF == refs/heads/* ]]; then
VERSION=${GITHUB_REF#refs/heads/}
TAGS="${DOCKER_IMAGE}:${VERSION}"
fi
# If the VERSION looks like a version number, also tag as 'latest'
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
fi
# Set output parameters
echo ::set-output name=tags::${TAGS}
echo ::set-output name=docker_image::${DOCKER_IMAGE}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/ppc64le
push: true
tags: ${{ steps.prep.outputs.tags }}

View file

@ -1,67 +0,0 @@
---
name: 'Build Docker Images'
on:
release:
types: [published]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v3
- name: Prepare
id: prep
run: |
DOCKER_IMAGE=${{ secrets.DOCKER_USERNAME }}/${GITHUB_REPOSITORY#*/}
# If this is git tag, use the tag name as a docker tag
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/webssh2-v}
TAGS="${DOCKER_IMAGE}:${VERSION}"
fi
# If this is git branch, use the branch name as a docker tag
if [[ $GITHUB_REF == refs/heads/* ]]; then
VERSION=${GITHUB_REF#refs/heads/}
TAGS="${DOCKER_IMAGE}:${VERSION}"
fi
# If the VERSION looks like a version number, assume that
# this is the most recent version of the image and also
# tag it 'latest'. This is done by just specifying the ${DOCKER_IMAGE}
# without a tag.
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
TAGS="$TAGS,${DOCKER_IMAGE}"
fi
# Set output parameters.
echo ::set-output name=tags::${TAGS}
echo ::set-output name=docker_image::${DOCKER_IMAGE}
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build
uses: docker/build-push-action@v2
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/ppc64le
push: true
tags: ${{ steps.prep.outputs.tags }}

View file

@ -1,47 +0,0 @@
---
name: 'Create Release'
on:
push:
branches:
- main
paths-ignore:
- '.github/**'
- '.devcontainer/**'
- '.**'
- '**.md'
jobs:
release:
runs-on: ubuntu-latest
outputs:
paths_released: ${{ steps.manifest_release.outputs.paths_released }}
steps:
- uses: google-github-actions/release-please-action@v3
id: manifest_release
with:
token: ${{ secrets.RELEASE_PLEASE_UAT }}
command: manifest
package-name: webssh2
path: app
default-branch: main
release-type: node
publish:
runs-on: ubuntu-20.04
needs: release
strategy:
fail-fast: false
matrix:
path: ${{fromJson(needs.release.outputs.paths_released)}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 16
registry-url: 'https://registry.npmjs.org'
- name: publish-to-npm
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
run: |
cd ${{ matrix.path }}
npm install
npx lerna bootstrap
npx lerna publish from-package --no-push --no-private --yes

3
.gitignore vendored
View file

@ -48,5 +48,4 @@ jspm_packages
#Mac Files #Mac Files
.DS_Store .DS_Store
Build/Release Build/Release
app/bob_rsa

View file

@ -1,3 +0,0 @@
{
"app": "0.5.0-pre-4"
}

View file

@ -1,4 +1,4 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.22.1 version: v1.13.1
ignore: {} ignore: {}
patch: {} patch: {}

1
.tool-versions Normal file
View file

@ -0,0 +1 @@
nodejs 6.9.1

View file

@ -1,6 +1,4 @@
language: node_js language: node_js
node_js: node_js:
- 14 - 6
- 16 - 10
before_install:
- npm i -g snyk

View file

@ -1,13 +0,0 @@
# Buliding
To rebuild the client files, you need at least Node v14.
The source of the client files are located in `./app/client/src`
`npm run build` will compile the source files there into `./app/client/public/`. This directory is considered to be volitile and is deleted every time `npm run build` is invoked.
WebPack is used for building and the configuration is located in `./app/scripts`
If one wishes to make changes to the javascript, the html, or the css it should be done in `./app/client/src` and then complied using `npm run build`
For development purposes, you may also utilize `npm run builddev` which will not minimize the source and allow you to more easily troubleshoot while making customizations.

Binary file not shown.

View file

@ -0,0 +1 @@
e2e70f7d2949b6c8fe0299f888a3725763a62c01a1faea1fb729babc2ed51c92 Build/Release/BIG-IP-ILX-WebSSH2-0.2.8.tgz

View file

@ -1,3 +1,333 @@
# Changelog Moved # Change Log
See [app/CHANGELOG.md](app/CHANGELOG.md) ## [0.2.13] 2024-07-11
BIG-IP Specific version
### Fixes
- fixed missing reference to `read-config-ng` switchover which could prevent `config.json` from being read
## [0.2.12] 2024-07-10
BIG-IP Specific version
### Changes
- `[ctrl]+[shift]+[6]` or `[ctrl]+[^]` now sends `RS` or `0x1E`
## [0.2.11] 2020-05-12
BIG-IP Specific version
### BREAKING
- Not compatible with versions of ephemeral_auth before 0.4.8 due to child resources moving under /ssh
### Changes
- in `config.json.sample` - `allowreauth` set to `false` by default
- in `config.json.sample` - potential future proofing for CORS support `http.origins`
- `ssh` module updated to 0.8.9
- Move all child resources to start from under /ssh
- /socket.io -> /ssh/socket.io
- /webssh2.css -> /ssh/webssh2.css
- /webssh2.bundle.js -> /ssh/webssh2.bundle.js
- /reauth -> /ssh/reauth
- perhaps more
## [0.2.10] not actually released
## [0.2.9] 2019-06-13
### Changes
- Missing require('fs') in `server/app.js` See issue [#135](../../issues/135)
- Patched read-config to mitigate vulnerability in js-yaml
- issue not exploitable on webssh2 implementation
- patched anyway
- sending my patch upstream to read-config, webssh2 package.json points to patched version in my repository https://github.com/billchurch/nodejs-read-config
- See https://github.com/nodeca/js-yaml/issues/475 for more detail
## [0.2.8] 2019-05-25
### Changes
- Fixes issue if no password is entered, browser must be closed and restart to attempt to re-auth. See issue [#118](../../issues/118). Thanks @smilesm2 for the idea.
- fixes broken `npm run (build|builddev)`
- update font-awesome fonts to 5.6.3
- update webpack and dependancies
- update xterm to 3.8.0
### Fixes
- ILX workspace may not always import properly due to symbolic links (specifically ./node_modules/.bin). This is removed from the ILX package
## [0.2.7] 2018-11-11
### Changes
- `config.reauth` was not respected if initial auth presented was incorrect, regardless of `reauth` setting in `config.json` reauth would always be attempted. fixes [#117](../../issues/117)
- **BREAKING** moved app files to /app, this may be a breaking change
- Updated dockerfile for new app path
- Updated app dependancies
- xterm v3.8.0
- https://github.com/xtermjs/xterm.js/releases/tag/3.8.0
- basic-auth v2.0.1
- https://github.com/jshttp/basic-auth/releases/tag/v2.0.1
- express v4.16.4
- https://github.com/expressjs/express/releases/tag/4.16.4
- validator v10.9.0
- https://github.com/chriso/validator.js/releases/tag/10.9.0
- Updated dev dependancies
- snazzy v8.0.0
- standard v12.0.1
- uglifyjs-webpack-plugin v2.0.1
- ajv v6.5.5
- copy-webpack-plugin v4.6.0
- css-loader v1.0.1
- nodemon v1.18.6
- postcss-discard-comments v4.0.1
- snyk v1.108.2
- url-loader v1.1.2
- webpack v4.25.1
- webpack-cli v3.1.2
## [0.2.6] 2018-11-09
### Changes
- Reauth didn't work if intial auth presented was incorrect, (see issue #112) fixed thanks @vvalchev
- Update node version supported to >=6 (PR #115) thanks @perlun
- Update packages
- developer dependencies
## [0.2.5] 2018-09-11
### Added
- Reauth function thanks to @vbeskrovny and @vvalchev (9bbc116)
- Controlled by `config.json` option `options.allowreauth` true presents reauth dialog and false hides dialog
### Changed
- `options.challengeButton` enabled
- previously this configuration option did nothing, this now enables the Credentials button site-wide regardless of the `allowreplay` header value
- Updated debug module to v4
## [0.2.4] 2018-07-18
### Added
- Browser title window now changes with xterm escape sequences (see http://tldp.org/HOWTO/Xterm-Title-3.html)
- Added bellStyle options
- `GET var`: **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound", "none"
- `config.json`: **terminal.bellStyle** - _string_ - Style of terminal bell: (sound|none). **Default:** "sound".
- `workspace` folder on GITHUB for BIG-IP specific fixes/changes
### Changed
- Updated xterm.js to 3.1.0
- https://github.com/xtermjs/xterm.js/releases/tag/3.1.0
- Default listen IP in `config.json` changed back to 127.0.0.1
### Fixed
- ESC]0; is now removed from log files when using the browser-side logging feature
## [0.2.3] unreleased
### Fixed
- ESC]0; is now removed from log files when using the browser-side logging feature
## [0.2.0] 2018-02-10
Mostly client (browser) related changes in this release
### Added
- Menu system
- Fontawesome icons
- Resizing browser window sends resize events to terminal container as well as SSH session (pty)
- New terminal options (config.json as well as GET vars)
- terminal.cursorBlink - boolean - Cursor blinks (true), does not (false) Default: true.
- terminal.scrollback - integer - Lines in the scrollback buffer. Default: 10000.
- terminal.tabStopWidth - integer - Tab stops at n characters Default: 8.
- New serverside (nodejs) terminal configuration options (cursorBlink, scrollback, tabStopWidth)
- Logging of MRH session (unassigned if not present)
- Express compression feature
### Changed
- Updated xterm.js to 3.0.2
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.2
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.1
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.0
- Moved javascript events out of html into javascript
- Changed asset packaging from grunt to Webpack to be inline with xterm.js direction
- Moved logging and credentials buttons to menu system
- Removed non-minified options (if you need to disable minification, modify webpack scripts and 'npm run build')
### Fixed
- Resolved loss of terminal foucs when interacting with option buttons (Logging, etc...)
# Change Log
## [0.1.4] 2018-01-30
### Changed
- Moved socket and util out of folders into .js in root.
- added keepaliveInterval and keepaliveCountMax config options
## [0.1.3] 2017-09-28
### Changed
- Upgrade to debug@3.1 to eliminate ReDoS in %o formatter
- Upgrade Express to 4.15.5 for ReDOS
- Upgrade basic-auth to v2.0
## [0.1.2] 2017-07-31
### Added
- ssh.readyTimeout option in config.json (time in ms, default 20000, 20sec)
### Changed
- Updated xterm.js to 2.9.2 from 2.6.0
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.2
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.1
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.0
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.1
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.0
- See https://github.com/sourcelair/xterm.js/releases/tag/2.7.0
- Updated ssh2 to 0.5.5 to keep current, no fixes impacting WebSSH2
- ssh-streams to 0.1.19 from 0.1.16
- Updated validator.js to 8.0.0, no fixes impacting WebSSH2
- https://github.com/chriso/validator.js/releases/tag/8.0.0
- Updated Express to 4.15.4, no fixes impacting WebSSH2
- https://github.com/expressjs/express/releases/tag/4.15.4
- Updated Express-session to 1.15.5, no fixes impacting WebSSH2
- https://github.com/expressjs/session/releases/tag/v1.15.5
- Updated Debug to 3.0.0, no fixes impacting WebSSH2
- https://github.com/visionmedia/debug/releases/tag/3.0.0
- Running in strict mode ('use strict';)
## [0.1.1] 2017-06-03
### Added
- `serverlog.client` and `serverlog.server` options added to `config.json` to enable logging of client commands to server log (only client portion implemented at this time)
- morgan express middleware for logging
### Changed
- Updated socket.io to 1.7.4
- continued refactoring, breaking up `index.js`
- revised error handling methods
- revised session termination methods
### Fixed
### Removed
- color console decorations from `util/index.js`
- SanatizeHeaders function from `util/index.js`
## [0.1.0] 2017-05-27
### Added
- This ChangeLog.md file
- Support for UTF-8 characters (thanks @bara666)
- Snyk, Bithound, Travis CI
- Cross platform improvements (path mappings)
- Session fixup between Express and Socket.io
- Session secret settings in `config.json`
- env variable `DEBUG=ssh2` will put the `ssh2` module into debug mode
- env variable `DEBUG=WebSSH2` will output additional debug messages for functions
and events in the application (not including the ssh2 module debug)
- using Grunt to pull js and css source files from other modules `npm run build` to rebuild these if changed or updated.
- `useminified` option in `config.json` to enable using minified client side javascript (true) defaults to false (non-minified)
- sshterm= query option to specify TERM environment variable for host, valid strings are alpha-numeric with a hypen (validated). Otherwise the default ssh.term variable from `config.json` will be used.
- validation for host (v4,v6,fqdn,hostname), port (integer 2-65535), and header (sanitized) from URL input
### Changed
- error handling in public/client.js
- moved socket.io operations to their own file /socket/index.js, more changes like this to come (./socket/index.js)
- all session based variables are now under the req.session.ssh property or socket.request.ssh (./index.js)
- moved SSH algorithms to `config.json` and defined as a session variable (..session.ssh.algorithms)
-- prep for future feature to define algorithms in header or some other method to enable separate ciphers per host
- minified and combined all js files to a single js in `./public/webssh2.min.js` also included a sourcemap `./public/webssh2.min.js` which maps to `./public/webssh2.js` for easier troubleshooting.
- combined all css files to a single css in `./public/webssh2.css`
- minified all css files to a single css in `./public/webssh2.min.css`
- copied all unmodified source css and js to /public/src/css and /public/src/js respectively (for troubleshooting/etc)
- sourcemaps of all minified code (in /public/src and /public/src/js)
- renamed `client.htm` to `client-full.htm`
- created `client-min.htm` to serve minified javascript
- if header.text is null in `config.json` and header is not defined as a get parameter the Header will not be displayed. Both of these must be null / undefined and not specified as get parameters.
### Fixed
- Multiple errors may overwrite status bar which would cause confusion as to what originally caused the error. Example, ssh server disconnects which prompts a cascade of events (conn.on('end'), socket.on('disconnect'), conn.on('close')) and the original reason (conn.on('end')) would be lost and the user would erroneously receive a WEBSOCKET error as the last event to fire would be the websocket connection closing from the app.
- ensure ssh session is closed when a browser disconnects from the websocket
- if headerBackground is changed, status background is changed to the same color (typo, fixed)
### Removed
- Express Static References directly to module source directories due to concatenating and minifying js/css
## [0.0.5] - 2017-03-23
### Added
- Added experimental support for logging (see Readme)
### Fixed
- Terminal geometry now properly fills the browser screen and communicates this to the ssh session. Tested with IE 11 and recent versions of Chrome/Safari/Firefox.
## [0.0.4] - 2017-03-23
### Added
- Set default terminal to xterm-color
- Mouse event support
- New config option, config.ssh.term to set terminal
### Changed
- Update to Xterm.js 2.4.0
- Minor code formatting cleanup
## [0.0.3] - 2017-02-16
### Changed
- Update xterm to latest (2.3.0)
### Fixed
- Fixed misspelled config.ssh.port property
## [0.0.2] - 2017-02-01
### Changed
- Moving terminal emulation to xterm.js
- updating module version dependencies
### Fixed
- Fixed issue with banners not being displayed properly from UNIX hosts when only lf is used
## [0.0.1] - 2016-06-28
### Added
- Initial proof of concept and release. For historical purposes only.

View file

@ -1,9 +1,7 @@
FROM node:16-alpine FROM node:8.6
RUN apk update && apk add bash
WORKDIR /usr/src WORKDIR /usr/src
COPY app/ /usr/src/ COPY app/ /usr/src/
RUN npm ci --audit=false --bin-links=false --fund=false RUN npm install --production
EXPOSE 2222/tcp EXPOSE 2222
ENTRYPOINT [ "/usr/local/bin/node", "index.js" ] CMD npm run start

15
ISSUE_TEMPLATE.md Normal file
View file

@ -0,0 +1,15 @@
Depending on the type of issue, please include the follwing information:
- Node and NPM Version
- `node -v`
- `npm -v`
- Server OS Version / Distribution / Processor Architecture
- `uname -a`
- `cat /etc/os-release`
- WebSSH2 release version
- `grep version app/package.json`
- OS and Version of SSH server connecting to
- `uname -a`
- `sshd -v`
- Browser Version and OS
- Information from brwoser's About... or a screenshot of the about screen.
- Any log or messages from the WebSSH2 output

15
Jenkinsfile vendored Normal file
View file

@ -0,0 +1,15 @@
pipeline {
agent {
docker {
image 'node:6-alpine'
args '-p 3000:3000'
}
}
stages {
stage('Build') {
steps {
sh 'npm install'
}
}
}
}

View file

@ -1,2 +0,0 @@
test:
cd ./app; npm run test

303
README.md
View file

@ -1,6 +1,5 @@
# WebSSH2 # WebSSH2
[![GitHub version](https://badge.fury.io/gh/billchurch%2Fwebssh2.svg)](https://badge.fury.io/gh/billchurch%2Fwebssh2)
[![Build Status](https://travis-ci.com/billchurch/webssh2.svg?branch=main)](https://travis-ci.com/billchurch/webssh2) [![GitHub version](https://img.shields.io/github/v/release/billchurch/webssh2)](https://github.com/billchurch/webssh2/releases/latest) [![docker build images](https://github.com/billchurch/webssh2/actions/workflows/docker-multiplatform.yml/badge.svg)](https://github.com/billchurch/webssh2/actions/workflows/docker-multiplatform.yml)
[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/billchurch) [![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/billchurch)
@ -8,153 +7,54 @@ Web SSH Client using ssh2, socket.io, xterm.js, and express
A bare bones example of an HTML5 web-based terminal emulator and SSH client. We use SSH2 as a client on a host to proxy a Websocket / Socket.io connection to a SSH2 server. A bare bones example of an HTML5 web-based terminal emulator and SSH client. We use SSH2 as a client on a host to proxy a Websocket / Socket.io connection to a SSH2 server.
<img width="600" height="340" alt="WebSSH2 v0.2.0 demo" src="https://user-images.githubusercontent.com/1668075/182425293-acc8741e-cc92-4105-afdc-9538e1685d4b.gif"> <img width="600" height="340" alt="WebSSH2 v0.2.0 demo" src="https://github.com/billchurch/WebSSH2/raw/master/screenshots/demo-800.gif">
# Requirements # Requirements
Node v14.x or above. If using <v14.x you should be able to run by replacing the "read-config" package to @1 like this (after a clone): Node v6.x or above. If using <v6.x you should be able to run by replacing the "read-config" package to @1 like this (after a clone):
`npm install --save read-config@1 `npm install --save read-config@1
` `
Just keep in mind that there is no intention to ensure compatability with Node < v14 Just keep in mind that there is no intention to ensure compatability with Node < v6.x
# Instructions # Instructions
The instructions previously showed cloning directly from main, while normally safe may include "work in progress". It's recommended that after you clone, you switch to a particular tag.
To install: To install:
1. Clone to a location somewhere 1. Clone to a location somewhere and then `cd app` and `npm install --production`. If you want to develop and rebuild javascript and other files utilize `npm install` instead.
2. Checkout to the `current` tag using `git checkout current` or choose a particular [release](https://github.com/billchurch/webssh2/releases/) tag 2. If desired, edit config.json to change the listener to your liking. There are also some default options which may be definied for a few of the variables.
3. `cd app` and `npm install --production`. If you want to develop and rebuild javascript and other files utilize `npm install` instead. 3. Run `npm start`
4. If desired, edit app/config.json to change the listener to your liking. There are also some default options which may be definied for a few of the variables. 4. Fire up a browser, navigate to IP/port of your choice and specify a host (https isn't used here because it's assumed it will be off-loaded to
some sort of proxy):
5. Run `npm start`
6. Fire up a browser, navigate to IP/port of your choice and specify a host (https isn't used here because it's assumed it will be off-loaded to some sort of proxy):
http://localhost:2222/ssh/host/127.0.0.1 http://localhost:2222/ssh/host/127.0.0.1
You will be prompted for credentials to use on the SSH server via HTTP Basic authentcaiton. This is to permit usage with some SSO systems that can replay credentials over HTTP basic. You will be prompted for credentials to use on the SSH server via HTTP Basic authentcaiton. This is to permit usage with some SSO systems that can replay credentials over HTTP basic.
Alternatively in main for testing, you can send credentials via POST with the variables "username" and "userpassword". # Docker Instructions
# Customizing client files Modify config.json
See [BUILDING.md](BUILDING.md) for more details. ```json
# Docker
## NOTICE
Docker versions differ from what is in `main` and are release dependant.
Meaning billchurch/webssh2:latest is the latest official release. This does NOT sync with what is in `main` on this repo. `main` is development and will change until it gets a release tag.
On occasion, examples or instructions on `main` will drift from what is released. You should refer to the tag of the version you're using to ensure you are following the proper guidance.
That being said the most current release version is [0.4.6](https://github.com/billchurch/webssh2/tree/0.4.6), see that tag for details.
## Instructions
Some configuration options are available as [Environment Variables](#environment-variables). If there is a configuration option you require which does not have an environment variable please [open an issue requesting](../../issues/new/choose).
[webssh2 images are available in docker hub](https://hub.docker.com/repository/docker/billchurch/webssh2).
the `latest` tag will pull the most recent stable release, otherwise you can pull individual tags/releases/versions of this repo by using a particular version in the tag.
For instance:
```docker pull billchurch/webssh2:0.4.6```
or
```docker pull billchurch/webssh2:0.4.7-alpha.2```
or
```docker pull billchurch/webssh2```
for the most recent
If you want to play around localy:
Copy app/config.json.template to app/config.json and modify the latter:
```js
{ {
// ...
"listen": { "listen": {
"ip": "0.0.0.0", "ip": "0.0.0.0",
"port": 2222 "port": 2222
} }
// ...
} }
``` ```
Rebuild and run Build and run
```bash ```bash
docker build -t webssh2 . docker build -t webssh2 .
docker run --name webssh2 -d -p 2222:2222 webssh2 docker run --name webssh2 -d -p 2222:2222 webssh2
``` ```
Alternatively if you don't want to rebuild, mount the config at runtime:
```bash
docker run --name webssh2 -d -p 2222:2222 -v `pwd`/app/config.json:/usr/src/config.json webssh2
```
Alternatively if you don't want to build either and mount the config at runtime relying on the community image :
```bash
docker run --name webssh2 -d -p 2222:2222 -v `pwd`/app/config.json:/usr/src/config.json billchurch/webssh2
```
# Options # Options
## Environment Variables
Environment variables introduced in 0.4.7 will override anything specified in `config.json`. This is useful for settings that may be per-node, or in a container environment. These are optional and will default to whatever their peer config.json settings are
* **LISTEN** - _string_ - IP address node should listen on for client connections, defaults to `127.0.0.1`. Peer is **listen.ip**
* **PORT** - _integer_ - Port node should listen on for client connections, defaults to `2222`. Peer is **listen.port**
* **SOCKETIO_ORIGINS** - _array_ - COORS origins to allow connections from to socket.io server, defaults to `localhost:2222`. Changed in 0.3.1, to enable previous, less secure, default behavior of everything use `*:*` (not recommended). Check [#240](../../issues/240). Peer is **socketio.origins**
* **SOCKETIO_PATH** _string_ - Path to socket.io client files. Default: `/ssh/socket.io`. Peer is **socketio.path**
* **SOCKETIO_SERVECLIENT** _boolean_ - serve the socket.io client. This is built into the custom javascript, so you shouldn't need this. Kept as an option just in case. Default: `false`. Peer is **socketio.serveClient**
## POST request vars (in main branch for testing)
* **username** - _string_ - username to log into ssh with
* **userpassword** _string_ password to log into ssh with
* **port=** - _integer_ - port of SSH server (defaults to 22)
* **header=** - _string_ - optional header to display on page
* **headerBackground=** - _string_ - optional background color of header to display on page
* **sshterm=** - _string_ - optional specify terminal emulation to use, defaults to `ssh.term` in `config.json` or `vt100` if that is null
* **readyTimeout=** - _integer_ - How long (in milliseconds) to wait for the SSH handshake to complete. **Default:** 20000. **Enforced Values:** Min: 1, Max: 300000
* **cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true.
* **scrollback** - _integer_ - Lines in the scrollback buffer. **Default:** 10000. **Enforced Values:** Min: 1, Max: 200000
* **tabStopWidth** - _integer_ - Tab stops at _n_ characters **Default:** 8. **Enforced Values:** Min: 1, Max: 100
* **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound", "none"
* **fontSize** - _number_ - Size of terminal font. **Default:** 12
* **fontFamily** - _string_ - Font family
* **letterSpacing** - _number_ - Letter spacing
* **lineHeight** - _number_ - Line height
## GET request vars ## GET request vars
* **port=** - _integer_ - port of SSH server (defaults to 22) * **port=** - _integer_ - port of SSH server (defaults to 22)
@ -163,8 +63,6 @@ Environment variables introduced in 0.4.7 will override anything specified in `c
* **headerBackground=** - _string_ - optional background color of header to display on page * **headerBackground=** - _string_ - optional background color of header to display on page
* **sshterm=** - _string_ - optional specify terminal emulation to use, defaults to `ssh.term` in `config.json` or `vt100` if that is null
* **readyTimeout=** - _integer_ - How long (in milliseconds) to wait for the SSH handshake to complete. **Default:** 20000. **Enforced Values:** Min: 1, Max: 300000 * **readyTimeout=** - _integer_ - How long (in milliseconds) to wait for the SSH handshake to complete. **Default:** 20000. **Enforced Values:** Min: 1, Max: 300000
* **cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true. * **cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true.
@ -175,14 +73,6 @@ Environment variables introduced in 0.4.7 will override anything specified in `c
* **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound", "none" * **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound", "none"
* **fontSize** - _number_ - Size of terminal font. **Default:** "12"
* **fontFamily** - _string_ - Font family
* **letterSpacing** - _number_ - Letter spacing
* **lineHeight** - _integer_ - Line height
## Headers ## Headers
* **allowreplay** - _boolean_ - Allow use of password replay feature, example `allowreplay: true` * **allowreplay** - _boolean_ - Allow use of password replay feature, example `allowreplay: true`
@ -196,23 +86,17 @@ Environment variables introduced in 0.4.7 will override anything specified in `c
* **listen.port** - _integer_ - Port node should listen on for client connections, defaults to `2222` * **listen.port** - _integer_ - Port node should listen on for client connections, defaults to `2222`
* **socketio.serveClient** - _boolean_ - serve the socket.io client. This is built into the custom javascript, so you shouldn't need this. Kept as an option just in case. Default: `false` * **http.origins** - _array_ - COORS origins to allow connections from to socket.io server, defaults to `localhost:2222`. Changed in 0.3.1, to enable previous, less secure, default behavior of everything use `*:*` (not recommended). Check [#240](../../issues/240)
* **socketio.path** - _string_ - Path to socket.io client files. Default: `/ssh/socket.io`
* **socketio.origins** - _array_ - COORS origins to allow connections from to socket.io server, defaults to `localhost:2222`. Changed in 0.3.1, to enable previous, less secure, default behavior of everything use `*:*` (not recommended). Check [#240](../../issues/240)
* **user.name** - _string_ - Specify user name to authenticate with. In normal cases this should be left to the default `null` setting. * **user.name** - _string_ - Specify user name to authenticate with. In normal cases this should be left to the default `null` setting.
* **user.password** - _string_ - Specify password to authenticate with. In normal cases this should be left to the default `null` setting. * **user.password** - _string_ - Specify password to authenticate with. In normal cases this should be left to the default `null` setting.
* **user.overridebasic** - _boolean_ - When set to `true` ignores `Authorization: Basic` header sent from client and use credentials defined in `user.name` and `user.password` instead. Defaults to `false`. [issue 242](../../issues/242) for more information.
* **ssh.host** - _string_ - Specify host to connect to. May be either hostname or IP address. Defaults to `null`. * **ssh.host** - _string_ - Specify host to connect to. May be either hostname or IP address. Defaults to `null`.
* **ssh.port** - _integer_ - Specify SSH port to connect to, defaults to `22` * **ssh.port** - _integer_ - Specify SSH port to connect to, defaults to `22`
* **ssh.term** - _string_ - Specify terminal emulation to use, defaults to `vt100` if null * **ssh.term** - _string_ - Specify terminal emulation to use, defaults to `xterm-color`
* **ssh.readyTimeout** - _integer_ - How long (in milliseconds) to wait for the SSH handshake to complete. **Default:** 20000. * **ssh.readyTimeout** - _integer_ - How long (in milliseconds) to wait for the SSH handshake to complete. **Default:** 20000.
@ -220,8 +104,6 @@ Environment variables introduced in 0.4.7 will override anything specified in `c
* **ssh.keepaliveCountMax** - _integer_ - How many consecutive, unanswered SSH-level keepalive packets that can be sent to the server before disconnection (similar to OpenSSH's ServerAliveCountMax config option). **Default:** 10. * **ssh.keepaliveCountMax** - _integer_ - How many consecutive, unanswered SSH-level keepalive packets that can be sent to the server before disconnection (similar to OpenSSH's ServerAliveCountMax config option). **Default:** 10.
* **allowedSubnets** - _array_ - A list of subnets that the server is allowed to connect to via SSH. An empty array means all subnets are permitted; no restriction. **Default:** empty array.
* **terminal.cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true. * **terminal.cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true.
* **terminal.scrollback** - _integer_ - Lines in the scrollback buffer. **Default:** 10000. * **terminal.scrollback** - _integer_ - Lines in the scrollback buffer. **Default:** 10000.
@ -230,14 +112,6 @@ Environment variables introduced in 0.4.7 will override anything specified in `c
* **terminal.bellStyle** - _string_ - Style of terminal bell: (sound|none). **Default:** "sound". * **terminal.bellStyle** - _string_ - Style of terminal bell: (sound|none). **Default:** "sound".
* **terminal.fontSize** - _number_ - Size of terminal font. **Default:** 14.
* **terminal.fontFamily** - _string_ - Font family
* **terminal.letterSpacing** - _number_ - Letter spacing
* **terminal.lineHeight** - _number_ - Line height
* **header.text** - _string_ - Specify header text, defaults to `My Header` but may also be set to `null`. When set to `null` no header bar will be displayed on the client. * **header.text** - _string_ - Specify header text, defaults to `My Header` but may also be set to `null`. When set to `null` no header bar will be displayed on the client.
* **header.background** - _string_ - Header background, defaults to `green`. * **header.background** - _string_ - Header background, defaults to `green`.
@ -252,90 +126,91 @@ Environment variables introduced in 0.4.7 will override anything specified in `c
* **algorithms** - _object_ - This option allows you to explicitly override the default transport layer algorithms used for the connection. Each value must be an array of valid algorithms for that category. The order of the algorithms in the arrays are important, with the most favorable being first. Valid keys: * **algorithms** - _object_ - This option allows you to explicitly override the default transport layer algorithms used for the connection. Each value must be an array of valid algorithms for that category. The order of the algorithms in the arrays are important, with the most favorable being first. Valid keys:
* **kex** - _array_ - Key exchange algorithms. * **kex** - _array_ - Key exchange algorithms.
* Default values: * Default values:
1. ecdh-sha2-nistp256 **(node v0.11.14 or newer)** 1. ecdh-sha2-nistp256
2. ecdh-sha2-nistp384 **(node v0.11.14 or newer)** 2. ecdh-sha2-nistp384
3. ecdh-sha2-nistp521 **(node v0.11.14 or newer)** 3. ecdh-sha2-nistp521
4. diffie-hellman-group-exchange-sha256 **(node v0.11.12 or newer)** 4. diffie-hellman-group-exchange-sha256
5. diffie-hellman-group14-sha1 5. diffie-hellman-group14-sha1
* Supported values: * Supported values:
* ecdh-sha2-nistp256 **(node v0.11.14 or newer)** * ecdh-sha2-nistp256
* ecdh-sha2-nistp384 **(node v0.11.14 or newer)** * ecdh-sha2-nistp384
* ecdh-sha2-nistp521 **(node v0.11.14 or newer)** * ecdh-sha2-nistp521
* diffie-hellman-group-exchange-sha256 **(node v0.11.12 or newer)** * diffie-hellman-group-exchange-sha256
* diffie-hellman-group14-sha1 * diffie-hellman-group14-sha1
* diffie-hellman-group-exchange-sha1 **(node v0.11.12 or newer)** * diffie-hellman-group-exchange-sha1
* diffie-hellman-group1-sha1 * diffie-hellman-group1-sha1
* **cipher** - _array_ - Ciphers. * **cipher** - _array_ - Ciphers.
* Default values: * Default values:
1. aes128-ctr 1. aes128-ctr
2. aes192-ctr 2. aes192-ctr
3. aes256-ctr 3. aes256-ctr
4. aes128-gcm **(node v0.11.12 or newer)** 4. aes128-gcm
5. aes128-gcm@openssh.com **(node v0.11.12 or newer)** 5. aes128-gcm@openssh.com
6. aes256-gcm **(node v0.11.12 or newer)** 6. aes256-gcm
7. aes256-gcm@openssh.com **(node v0.11.12 or newer)** 7. aes256-gcm@openssh.com
8. aes256-cbc **legacy cipher for backward compatibility, should removed :+1:**
* Supported values: * Supported values:
* aes128-ctr * aes128-ctr
* aes192-ctr * aes192-ctr
* aes256-ctr * aes256-ctr
* aes128-gcm **(node v0.11.12 or newer)** * aes128-gcm
* aes128-gcm@openssh.com **(node v0.11.12 or newer)** * aes128-gcm@openssh.com
* aes256-gcm **(node v0.11.12 or newer)** * aes256-gcm
* aes256-gcm@openssh.com **(node v0.11.12 or newer)** * aes256-gcm@openssh.com
* aes256-cbc * aes256-cbc
* aes192-cbc * aes192-cbc
* aes128-cbc * aes128-cbc
* blowfish-cbc * blowfish-cbc
* 3des-cbc * 3des-cbc
* arcfour256 * arcfour256
* arcfour128 * arcfour128
* cast128-cbc * cast128-cbc
* arcfour * arcfour
* **hmac** - _array_ - (H)MAC algorithms. * **hmac** - _array_ - (H)MAC algorithms.
* Default values: * Default values:
1. hmac-sha2-256 1. hmac-sha2-256
2. hmac-sha2-512 2. hmac-sha2-512
3. hmac-sha1 3. hmac-sha1 **legacy hmac for backward compatibility, should removed :+1:**
* Supported values: * Supported values:
* hmac-sha2-256 * hmac-sha2-256
* hmac-sha2-512 * hmac-sha2-512
* hmac-sha1 * hmac-sha1
* hmac-md5 * hmac-md5
* hmac-sha2-256-96 * hmac-sha2-256-96
* hmac-sha2-512-96 * hmac-sha2-512-96
* hmac-ripemd160 * hmac-ripemd160
* hmac-sha1-96 * hmac-sha1-96
* hmac-md5-96 * hmac-md5-96
* **compress** - _array_ - Compression algorithms. * **compress** - _array_ - Compression algorithms.
* Default values: * Default values:
1. none 1. none
2. zlib@openssh.com 2. zlib@openssh.com
3. zlib 3. zlib
* Supported values: * Supported values:
* none * none
* zlib@openssh.com * zlib@openssh.com
* zlib * zlib
* **serverlog.client** - _boolean_ - Enables client command logging on server log (console.log). Very simple at this point, buffers data from client until it receives a line-feed then dumps buffer to console.log with session information for tracking. Will capture anything send from client, including passwords, so use for testing only... Default: false. Example: * **serverlog.client** - _boolean_ - Enables client command logging on server log (console.log). Very simple at this point, buffers data from client until it receives a line-feed then dumps buffer to console.log with session information for tracking. Will capture anything send from client, including passwords, so use for testing only... Default: false. Example:
* _serverlog.client: GcZDThwA4UahDiKO2gkMYd7YPIfVAEFW/mnf0NUugLMFRHhsWAAAA host: 192.168.99.80 command: ls -lat_ * _serverlog.client: GcZDThwA4UahDiKO2gkMYd7YPIfVAEFW/mnf0NUugLMFRHhsWAAAA host: 192.168.99.80 command: ls -lat_
@ -344,24 +219,12 @@ Environment variables introduced in 0.4.7 will override anything specified in `c
* **accesslog** - _boolean_ - http style access logging to console.log, default: false * **accesslog** - _boolean_ - http style access logging to console.log, default: false
* **safeShutdownDuration** - _integer_ - maximum delay, in seconds, given to users before the server stops when doing a safe shutdown # Experimental client-side logging
# Client-side logging
Clicking `Start logging` on the status bar will log all data to the client. A `Download log` option will appear after starting the logging. You may download at any time to the client. You may stop logging at any time my pressing the `Logging - STOP LOG`. Note that clicking the `Start logging` option again will cause the current log to be overwritten, so be sure to download first. Clicking `Start logging` on the status bar will log all data to the client. A `Download log` option will appear after starting the logging. You may download at any time to the client. You may stop logging at any time my pressing the `Logging - STOP LOG`. Note that clicking the `Start logging` option again will cause the current log to be overwritten, so be sure to download first.
# Example: # Example:
http://localhost:2222/ssh/host/192.168.1.1?port=2244&header=My%20Header&headerBackground=red http://localhost:2222/ssh/host/192.168.1.1?port=2244&header=My%20Header&headerBackground=red
# CONTRIBUTING
As of 0.4.0, we're trying our best to conform to the [Airbnb Javascript Style Guide](https://airbnb.io/projects/javascript/). I'm hoping this will make contributions easier and keep the code readable. I love shortcuts more than anyone but I've found when making changes to code I've not looked at in a while, it can take me a few momements to deconstruct what was being done due to readbility issues. While I don't agree with every decision in the style guide (semi-colons, yuk), it is a good base to keep the code consistent.
If you've not used it before, I recommend installing the [vscode extensions](https://blog.echobind.com/integrating-prettier-eslint-airbnb-style-guide-in-vscode-47f07b5d7d6a) for that and [Prettier](https://prettier.io/) and getting familiar. The autocorrections are great (especially if you hate dealing with semi-colons...)
All contributions are welcome, all may not make it into a release... To increase the chances of your contribution making it into a release, try your best to conform to the style guides and targets of the project.
# Tips # Tips
* You can enable extended debug messages in the browser Java console using: * If you want to add custom JavaScript to the browser client you can either modify `./src/client.html` and add a **<script>** element, modify `./src/index.js` directly, or check out `webpack.*.js` and add your custom javascript file to a task there (best option).
* `localStorage.debug = '*';` - Debug Everything (a lot of messages)
* `localStorage.debug = 'WebSSH2';` - Debug potentially interesting WebSSH2 related messages (replaying credentials, resizing data, other control messages)
* If you want to add custom JavaScript to the browser client you can either modify `./src/client.html` and add a **\<script\>** element, modify `./src/index.js` directly, or check out `webpack.*.js` and add your custom javascript file to a task there (best option).

View file

@ -1,16 +0,0 @@
# Security Policy
## Supported Versions
The following versions will get security updates.
| Version | Supported |
| ------- | ------------------ |
| 0.4.x | :white_check_mark: |
| 0.3.x | :x: |
| 0.2.x | :x: |
| 0.1.x | :x: |
## Reporting a Vulnerability
If you find a vulnerability, simply [open an issue](../../issues/new) with the details, use the label `security`.

View file

@ -1,24 +0,0 @@
{
"ignorePatterns": ["**/*{.,-}min.js"],
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"airbnb-base",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"prettier"
],
"rules": {
"prettier/prettier": ["error"]
}
}

View file

@ -1 +0,0 @@
tag-version-prefix=""

View file

@ -1,4 +0,0 @@
{
"printWidth": 100,
"singleQuote": true
}

View file

@ -1,461 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [0.5.0-pre-4](https://github.com/billchurch/webssh2/compare/webssh2-v0.4.7-pre-4...webssh2-v0.5.0-pre-4) (2022-08-07)
### Features
* test change for release ([476b566](https://github.com/billchurch/webssh2/commit/476b566c08a84bd35aaccf847253875b2c3afb10))
## [0.4.7-pre-4](https://github.com/billchurch/webssh2/compare/webssh2-v0.4.7-pre-3...webssh2-v0.4.7-pre-4) (2022-08-03)
### Miscellaneous Chores
* release 0.4.7-pre-4 ([7d4ba87](https://github.com/billchurch/webssh2/commit/7d4ba87bc1c198600ea33ee220553ef46ea2a103))
## [0.4.7-pre-3](https://github.com/billchurch/webssh2/compare/webssh2-v0.4.7-pre-2...webssh2-v0.4.7-pre-3) (2022-08-03)
### Miscellaneous Chores
* release 0.4.7-pre-3 ([0c78c1f](https://github.com/billchurch/webssh2/commit/0c78c1f31cc6380b7f0706822fc418cfede11413))
## [0.4.7-pre-2](https://github.com/billchurch/webssh2/compare/webssh2-v0.4.6...webssh2-v0.4.7-pre-2) (2022-08-02)
### ⚠ BREAKING CHANGES
* validate referer to /reauth is valid
* bump xterm to 4.18.0
* consistent logging messages see #286
* config system changes #284 (#285)
### Features
* add additional params for POST requests [#290](https://github.com/billchurch/webssh2/issues/290) ([46c1560](https://github.com/billchurch/webssh2/commit/46c1560e3c126376e18124e14e5c7fb8c029a0a1))
* add additional vars to POST requests [#290](https://github.com/billchurch/webssh2/issues/290) ([0a4e419](https://github.com/billchurch/webssh2/commit/0a4e419fb371ae95340fa890497022a2aa9d063a))
* add fontFamily, letterSpacing, lineHeight ([97f3088](https://github.com/billchurch/webssh2/commit/97f3088780744e13a6724a4967a4896aac3f20d8))
* add fontSize option [#292](https://github.com/billchurch/webssh2/issues/292) ([5e78812](https://github.com/billchurch/webssh2/commit/5e788129744d326e78ec91bda86ed5cecfd70d3f))
* config system changes [#284](https://github.com/billchurch/webssh2/issues/284) ([#285](https://github.com/billchurch/webssh2/issues/285)) ([9c99b09](https://github.com/billchurch/webssh2/commit/9c99b0940ec726193deae3c4999d25a297874d67))
* consistent logging messages see [#286](https://github.com/billchurch/webssh2/issues/286) ([50cfcb9](https://github.com/billchurch/webssh2/commit/50cfcb97788cbd3409b4605adceef3d47e370e38))
* credentials over http post for [#290](https://github.com/billchurch/webssh2/issues/290) ([5b8f88c](https://github.com/billchurch/webssh2/commit/5b8f88cfef1745c88748277217204e6c38c7ff7e))
* reorder viewport setup at ssh handshake [#292](https://github.com/billchurch/webssh2/issues/292) ([140e1e2](https://github.com/billchurch/webssh2/commit/140e1e24b14d6b74848e9d250c2b44f806ad627d))
* validate referer to /reauth is valid ([0dcaa6e](https://github.com/billchurch/webssh2/commit/0dcaa6e15062cdc3252ce52abd9057caf4c00a30))
### Bug Fixes
* Fix the parameter passing problem of setDefaultCredentials to make it perform data initialization normally ([#288](https://github.com/billchurch/webssh2/issues/288)) ([40cbb35](https://github.com/billchurch/webssh2/commit/40cbb35616fa17c1c36520690f40ebce0b488153))
* invalid css in style.css ([ffab534](https://github.com/billchurch/webssh2/commit/ffab5345dcb568fa2bb50a96f403174ad3728286))
### package
* bump xterm to 4.18.0 ([84c09ec](https://github.com/billchurch/webssh2/commit/84c09ec8a1909e4bbd0051debdbb905276a4245e))
### [0.4.6](https://github.com/billchurch/WebSSH2/compare/v0.2.10-0...v0.4.6) (2022-04-17)
### Features
* add SIGTERM to safe shutdown feature ([675b4f5](https://github.com/billchurch/WebSSH2/commit/675b4f5a3a92b187b620684eb1ce1b7afa0e2e08))
* **auth:** ssh private key auth implemented via config.json ([#161](https://github.com/billchurch/WebSSH2/issues/161)) ([342df8e](https://github.com/billchurch/WebSSH2/commit/342df8eb9cafba52eb63b50a60e11e1431d6fbd4))
* **config:** specify local source address and port for client connections fixes [#152](https://github.com/billchurch/WebSSH2/issues/152) ([#158](https://github.com/billchurch/WebSSH2/issues/158)) ([65d6ec6](https://github.com/billchurch/WebSSH2/commit/65d6ec68452b80c42fd62534355e456ce1f16a32))
* CORS support ([b324f33](https://github.com/billchurch/WebSSH2/commit/b324f338adeb3518322941639fb83ba9370814cc)), closes [#240](https://github.com/billchurch/WebSSH2/issues/240)
### Bug Fixes
* deprecated term.setOption ([d903da8](https://github.com/billchurch/WebSSH2/commit/d903da87c41882a3736683c7de497cb8bd37f885))
* dockerignore ([#272](https://github.com/billchurch/WebSSH2/issues/272)) ([8a68cca](https://github.com/billchurch/WebSSH2/commit/8a68ccaffa374584b5d9531f9dbeae616bd971f5))
* fixes default for allowreauth ([#239](https://github.com/billchurch/WebSSH2/issues/239)) ([dcfd81b](https://github.com/billchurch/WebSSH2/commit/dcfd81b454b9fe66edec489266dc35a765464c6b)), closes [#238](https://github.com/billchurch/WebSSH2/issues/238)
* missing ENTRYPOINT for Dockerfile ([6a3a47a](https://github.com/billchurch/WebSSH2/commit/6a3a47a13de3cd70d603379a27e055f08a6ee62c))
* obey host ssh.host in config fixes [#190](https://github.com/billchurch/WebSSH2/issues/190) ([7b7e8e7](https://github.com/billchurch/WebSSH2/commit/7b7e8e753358ed48f52eb9aa2fc359bf758f304b))
* subnet unauthorized now emits "ssherror" which persists across websocket termination ([e796f9f](https://github.com/billchurch/WebSSH2/commit/e796f9fb5874d6557433f25e8976b7aa58fa8144))
* update config.json.sample ([#177](https://github.com/billchurch/WebSSH2/issues/177)) ([42f973b](https://github.com/billchurch/WebSSH2/commit/42f973b4796f7f50237dc8ce613e477aa89352ca))
* update read-config-ng to 3.0.5, fixes [#277](https://github.com/billchurch/WebSSH2/issues/277) ([3e82c0d](https://github.com/billchurch/WebSSH2/commit/3e82c0dc4d31d1c97a7cf98139ef8e6dc0213b22))
* update xterm.js fixes [#261](https://github.com/billchurch/WebSSH2/issues/261) ([c801ef9](https://github.com/billchurch/WebSSH2/commit/c801ef9e5826e13a403a6462241cf8a4ff456d45))
## 0.4.5 [20220417]
### Fixes
- update read-config-ng to 3.0.5, fixes [#277](../../issues/277)
## 0.4.5 [20220331]
### Fixes
- Update socket.io to 4.2.0
- Update read-config-ng to 3.0.4
## 0.4.4 [20211209]
### Fixes
- Add ./node_modules to .dockerignore [#240](../../issues/240) thanks @UncleSamSwiss
- validator to 13.7.0 [to mitigate potential Regular Expression Denial of Service (ReDoS)](https://snyk.io/vuln/SNYK-JS-VALIDATOR-1090600)
- cidr-matcher should be [re-installed to pickup >json-schema@4.0.0 due to prototype pollution vulnerability](https://snyk.io/vuln/SNYK-JS-JSONSCHEMA-1920922)
- Update xterm.js to 4.15.0 [#261](../../issues/261)
- Replace deprecated term.setOptions with term.options
### Changes
- update README.md for additional Docker methods thanks @Utopiah
## 0.4.3 [20211019]
- update dependencies
- ssh2 to 1.4.0 [to mitigate potential command injection in windows](https://snyk.io/vuln/SNYK-JS-SSH2-1656673)
## 0.4.2 [20210813]
### changes
- update dependencies
- socket.io to 4.1.1
- read-config-ng to 3.0.2
- debug to 4.3.1
## 0.4.1 [20210703]
### Fixes
- lost comma in config.json.sample 71fe377
### Changes
- bump ws@7.4.6 to [mitigate potential ReDoS vulnerability](https://github.com/websockets/ws/releases/tag/7.4.6)
- dev: update CI tools
- dev: update dev tools
- dev: update build tools
## 0.4.0 [20210519]
### BREAKING
- Disabled ssh.serverlog.client option, this disables the POC which allowed for logging of the data sent between the client/server to the console.log.
- Dropping support for node versions under 14
### Changes
- Removed HTML menu code from ./app/server/socket.js, the menu is now fully laid out in the ./app/client/src/index.html and the option elements are hidden by default. Not sure why it wasn't done this way from the start, but there it is.
- Updated socket.io to v4.1.1
- Client javascript `./app/client/src/js/index.ts` is now built on TypeScript (`npm run build` will generate javascript for client and place into `app/client/public/webssh2.bundle.js` as before)
- Build environment changes
- removed unused xterm-addon-search, xterm-addon-weblinks, standard, postcss-discard-comments
- added prettier 2.3.0, typescript modules, socket.io-client 4.1.1, airbnb linting tools
### Added
- Lookup ip address for hostname in URL, fixes #199 thanks to @zwiy
- Ability to override `Authorization: Basic` header and replace with credentials specified in `config.json` fixes #243. New config.json option `user.overridebasic`
### CONTRIBUTING
In this release, we're trying our best to conform to the [Airbnb Javascript Style Guide](https://airbnb.io/projects/javascript/). I'm hoping this will make contributions easier and keep the code readable. I love shortcuts more than anyone but I've found when making changes to code I've not looked at in a while, it can take me a few momements to deconstruct what was being done due to readbility issues. While I don't agree with every decision in the style guide (semi-colons, yuk), it is a good base to keep the code consistent.
If you've not used it before, I recommend installing the [vscode extensions](https://blog.echobind.com/integrating-prettier-eslint-airbnb-style-guide-in-vscode-47f07b5d7d6a) for that and [Prettier](https://prettier.io/) and getting familiar. The autocorrections are great (especially if you hate dealing with semi-colons...)
As of 0.4.0-testing-0, the client code is written in [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html). It's not that much different from JavaScript, and the introduction strong typing will ultimately help to produce better code. Eventually we want to move the whole project to TypeScript but that make take a bit more time. Take a moment to look at ./app/client/src/js/index.ts to see what TypeScript looks like.
## 0.3.1 [20210513]
### BREAKING
- Ability to configure CORS settings for socket.io see [#240](../../issues/240) for more information on how this may break existing deployments. Default settings in example `config.json` are currently permissive `http.origins: ["*:*"]` please note that if a `config.json` is not present, the default is `http.origins: ["localhost:2222"]
### Added
- Safe Shutdown Feature - thanks to @edgarogh
- Sending SIGINT or SIGTERM to node process responsible for WebSSH2 or Docker process will result in a "safe" shutdown
- Timer is configured in config.safeShutdownDuration
- feat: Use docker build to create multi-arch images (#202)
### Fixed
- obey host ssh.host in config fixes #190
### Changed
- `config.json.sample`: `allowreauth` now defaults to `false` fixes #238
- update ssh2 to 0.8.8 -> 0.8.9 - [comparison at ssh2 repo](https://github.com/mscdex/ssh2/compare/v0.8.8...v0.8.9)
- update xterm to 4.12.0 [comparison at xtermjs repo](https://github.com/xtermjs/xterm.js/compare/4.4.0...4.12.0)
- update read-config-ng to 3.0.2
- update morgan to 1.10.0
- update debug to 4.3.1
- update express-session to 1.17.1
- update validator to 13.6.0
- development tools updates (build environment requires minimum of Node 10, only needed for customization)
- update @fortawesome/fontawesome-svg-core to 1.2.35
- update @fortawesome/free-solid-svg-icons to 5.15.3
- update copy-webpack-plugin to 8.1.1
- update cross-env to 7.0.3
- update css-loader to 5.2.4
- update file-loader to 6.2.0
- update mini-css-extract-plugin to 1.6.0
- update postcss-discard-comments to 5.0.0
- update snazzy to 9.0.0
- update standard to 16.0.3
- update standard-version to 9.3.0
- update style-loader to 2.0.0
- update terser-webpack-plugin to 5.1.1
- update url-loader to 4.1.1
- update webpack to 5.37.0
- update webpack-cli to 4.7.0
- update webpack-merge to 5.7.3
- update webpack-stream to 6.1.2
- update xterm-addon-fit to 0.5.0
- update xterm-addon-search to 0.8.0
- update xterm-addon-web-links to 0.4.0
- update ssri from 6.0.1 to 6.0.2 [#233](../../pull/233)
- update hosted-git-info from 2.8.5 to 2.8.9 [#237](../../pull/237)
- update lodash from 4.17.19 to 4.17.21 [#236](../../pull/236)
- update handlebars from 4.7.6 to 4.7.7 [#235](../../pull/235)
- update y18n from 4.0.0 to 4.0.1 [#230](../../pull/230)
- update elliptic from 6.5.3 to 6.5.4 [#228](../../pull/222833)
- update ini from 1.3.5 to 1.3.8 [#217](../../pull/217)
## 0.3.0 [20200315]
🍀🍀🍀
### Added
- Add configuration option to restrict connections to specified subnets thanks to @Mierdin
- favicon
- added module `serve-favicon` to serve favicon from root if pre-fetched by browser
- added `link rel=icon` line in client.htm to serve favico.ico out of /ssh/
### Changed
- Using new repo for read-config -> read-config-ng-
- removed express compression feature, added no real value.
- module updates
- ssh2 to 0.8.6 -> 0.8.8 - [comparison at ssh2 repo](https://github.com/mscdex/ssh2/compare/v0.8.6...v0.8.8)
- xterm 4.2.0 -> 4.4.0 - [comparison at xtermjs repo](https://github.com/xtermjs/xterm.js/compare/4.2.0...4.4.0)
- read-config-ng 3.0.1 - (taking over abandoned repo)n
- development module updates (does not impact production, only for development and rebuilding)
- fortawesome/fontawesome-svg-core 1.2.27
- fortawesome/free-solid-svg-icons 5.12.1
- standard-version 7.1.0
- webpack 4.42.0
- webpack-cli 3.3.11
- terser-webpack-plugin 2.3.5
- copy-webpack-plugin 5.1.1
- cross-env 7.0.2
- css-loader 3.4.2
- file-loader 5.1.0
- style-loader 1.1.3
- url-loader 3.0.0
### Potentially Breaking Changes
- Move all child resources to start from under /ssh
- /socket.io -> /ssh/socket.io
- /webssh2.css -> /ssh/webssh2.css
- /webssh2.bundle.js -> /ssh/webssh2.bundle.js
- /reauth -> /ssh/reauth
- perhaps more
### Fixes
- Typo in config.json.sample, thanks @wuchihsu, fixes #173
### Housekeeping
- Removed irrelavant build scripts from /scripts
## 0.2.9 [2019-06-13]
### Changes
- Missing require('fs') in `server/app.js` See issue [#135](../../issues/135)
- Patched read-config to mitigate vulnerability in js-yaml
- issue not exploitable on webssh2 implementation
- patched anyway
- sending my patch upstream to read-config, webssh2 package.json points to patched version in my repository https://github.com/billchurch/nodejs-read-config
- See https://github.com/nodeca/js-yaml/issues/475 for more detail
## 0.2.8 [2019-05-25]
### Changes
- Fixes issue if no password is entered, browser must be closed and restart to attempt to re-auth. See issue [#118](../../issues/118). Thanks @smilesm2 for the idea.
- fixes broken `npm run (build|builddev)`
- update font-awesome fonts to 5.6.3
- update webpack and dependancies
- update xterm to 3.8.0
### Fixes
- ILX workspace may not always import properly due to symbolic links (specifically ./node_modules/.bin). This is removed from the ILX package
## 0.2.7 [2018-11-11]
### Changes
- `config.reauth` was not respected if initial auth presented was incorrect, regardless of `reauth` setting in `config.json` reauth would always be attempted. fixes [#117](../../issues/117)
- **BREAKING** moved app files to /app, this may be a breaking change
- Updated dockerfile for new app path
- Updated app dependancies
- xterm v3.8.0
- https://github.com/xtermjs/xterm.js/releases/tag/3.8.0
- basic-auth v2.0.1
- https://github.com/jshttp/basic-auth/releases/tag/v2.0.1
- express v4.16.4
- https://github.com/expressjs/express/releases/tag/4.16.4
- validator v10.9.0
- https://github.com/chriso/validator.js/releases/tag/10.9.0
- Updated dev dependancies
- snazzy v8.0.0
- standard v12.0.1
- uglifyjs-webpack-plugin v2.0.1
- ajv v6.5.5
- copy-webpack-plugin v4.6.0
- css-loader v1.0.1
- nodemon v1.18.6
- postcss-discard-comments v4.0.1
- snyk v1.108.2
- url-loader v1.1.2
- webpack v4.25.1
- webpack-cli v3.1.2
## 0.2.6 [2018-11-09]
### Changes
- Reauth didn't work if intial auth presented was incorrect, (see issue #112) fixed thanks @vvalchev
- Update node version supported to >=6 (PR #115) thanks @perlun
- Update packages
- developer dependencies
## 0.2.5 [2018-09-11]
### Added
- Reauth function thanks to @vbeskrovny and @vvalchev (9bbc116)
- Controlled by `config.json` option `options.allowreauth` true presents reauth dialog and false hides dialog
### Changed
- `options.challengeButton` enabled
- previously this configuration option did nothing, this now enables the Credentials button site-wide regardless of the `allowreplay` header value
- Updated debug module to v4
## 0.2.4 [2018-07-18]
### Added
- Browser title window now changes with xterm escape sequences (see http://tldp.org/HOWTO/Xterm-Title-3.html)
- Added bellStyle options
- `GET var`: **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound "none"
- `config.json`: **terminal.bellStyle** - _string_ - Style of terminal bell: (sound|none). **Default:** "sound".
- `workspace` folder on GITHUB for BIG-IP specific fixes/changes
### Changed
- Updated xterm.js to 3.1.0
- https://github.com/xtermjs/xterm.js/releases/tag/3.1.0
- Default listen IP in `config.json` changed back to 127.0.0.1
### Fixed
- ESC]0; is now removed from log files when using the browser-side logging feature
## 0.2.3 unreleased
### Fixed
- ESC]0; is now removed from log files when using the browser-side logging feature
## 0.2.0 [2018-02-10]
Mostly client (browser) related changes in this release
### Added
- Menu system
- Fontawesome icons
- Resizing browser window sends resize events to terminal container as well as SSH session (pty)
- New terminal options (config.json as well as GET vars)
- terminal.cursorBlink - boolean - Cursor blinks (true), does not (false) Default: true.
- terminal.scrollback - integer - Lines in the scrollback buffer. Default: 10000.
- terminal.tabStopWidth - integer - Tab stops at n characters Default: 8.
- New serverside (nodejs) terminal configuration options (cursorBlink, scrollback, tabStopWidth)
- Logging of MRH session (unassigned if not present)
- Express compression feature
### Changed
- Updated xterm.js to 3.0.2
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.2
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.1
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.0
- Moved javascript events out of html into javascript
- Changed asset packaging from grunt to Webpack to be inline with xterm.js direction
- Moved logging and credentials buttons to menu system
- Removed non-minified options (if you need to disable minification, modify webpack scripts and 'npm run build')
### Fixed
- Resolved loss of terminal foucs when interacting with option buttons (Logging, etc...)
## 0.1.4 [2018-01-30]
### Changed
- Moved socket and util out of folders into .js in root.
- added keepaliveInterval and keepaliveCountMax config options
## 0.1.3 [2017-09-28]
### Changed
- Upgrade to debug@3.1 to eliminate ReDoS in %o formatter
- Upgrade Express to 4.15.5 for ReDOS
- Upgrade basic-auth to v2.0
## 0.1.2 [2017-07-31]
### Added
- ssh.readyTimeout option in config.json (time in ms, default 20000, 20sec)
### Changed
- Updated xterm.js to 2.9.2 from 2.6.0
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.2
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.1
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.0
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.1
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.0
- See https://github.com/sourcelair/xterm.js/releases/tag/2.7.0
- Updated ssh2 to 0.5.5 to keep current, no fixes impacting WebSSH2
- ssh-streams to 0.1.19 from 0.1.16
- Updated validator.js to 8.0.0, no fixes impacting WebSSH2
- https://github.com/chriso/validator.js/releases/tag/8.0.0
- Updated Express to 4.15.4, no fixes impacting WebSSH2
- https://github.com/expressjs/express/releases/tag/4.15.4
- Updated Express-session to 1.15.5, no fixes impacting WebSSH2
- https://github.com/expressjs/session/releases/tag/v1.15.5
- Updated Debug to 3.0.0, no fixes impacting WebSSH2
- https://github.com/visionmedia/debug/releases/tag/3.0.0
- Running in strict mode ('use strict';)
## 0.1.1 [2017-06-03]
### Added
- `serverlog.client` and `serverlog.server` options added to `config.json` to enable logging of client commands to server log (only client portion implemented at this time)
- morgan express middleware for logging
### Changed
- Updated socket.io to 1.7.4
- continued refactoring, breaking up `index.js`
- revised error handling methods
- revised session termination methods
### Fixed
### Removed
- color console decorations from `util/index.js`
- SanatizeHeaders function from `util/index.js`
## 0.1.0 [2017-05-27]
### Added
- This ChangeLog.md file
- Support for UTF-8 characters (thanks @bara666)
- Snyk, Bithound, Travis CI
- Cross platform improvements (path mappings)
- Session fixup between Express and Socket.io
- Session secret settings in `config.json`
- env variable `DEBUG=ssh2` will put the `ssh2` module into debug mode
- env variable `DEBUG=WebSSH2` will output additional debug messages for functions
and events in the application (not including the ssh2 module debug)
- using Grunt to pull js and css source files from other modules `npm run build` to rebuild these if changed or updated.
- `useminified` option in `config.json` to enable using minified client side javascript (true) defaults to false (non-minified)
- sshterm= query option to specify TERM environment variable for host, valid strings are alpha-numeric with a hypen (validated). Otherwise the default ssh.term variable from `config.json` will be used.
- validation for host (v4,v6,fqdn,hostname), port (integer 2-65535), and header (sanitized) from URL input
### Changed
- error handling in public/client.js
- moved socket.io operations to their own file /socket/index.js, more changes like this to come (./socket/index.js)
- all session based variables are now under the req.session.ssh property or socket.request.ssh (./index.js)
- moved SSH algorithms to `config.json` and defined as a session variable (..session.ssh.algorithms)
-- prep for future feature to define algorithms in header or some other method to enable separate ciphers per host
- minified and combined all js files to a single js in `./public/webssh2.min.js` also included a sourcemap `./public/webssh2.min.js` which maps to `./public/webssh2.js` for easier troubleshooting.
- combined all css files to a single css in `./public/webssh2.css`
- minified all css files to a single css in `./public/webssh2.min.css`
- copied all unmodified source css and js to /public/src/css and /public/src/js respectively (for troubleshooting/etc)
- sourcemaps of all minified code (in /public/src and /public/src/js)
- renamed `client.htm` to `client-full.htm`
- created `client-min.htm` to serve minified javascript
- if header.text is null in `config.json` and header is not defined as a get parameter the Header will not be displayed. Both of these must be null / undefined and not specified as get parameters.
### Fixed
- Multiple errors may overwrite status bar which would cause confusion as to what originally caused the error. Example, ssh server disconnects which prompts a cascade of events (conn.on('end'), socket.on('disconnect'), conn.on('close')) and the original reason (conn.on('end')) would be lost and the user would erroneously receive a WEBSOCKET error as the last event to fire would be the websocket connection closing from the app.
- ensure ssh session is closed when a browser disconnects from the websocket
- if headerBackground is changed, status background is changed to the same color (typo, fixed)
### Removed
- Express Static References directly to module source directories due to concatenating and minifying js/css
## 0.0.5 - [2017-03-23]
### Added
- Added experimental support for logging (see Readme)
### Fixed
- Terminal geometry now properly fills the browser screen and communicates this to the ssh session. Tested with IE 11 and recent versions of Chrome/Safari/Firefox.
## 0.0.4 - [2017-03-23]
### Added
- Set default terminal to xterm-color
- Mouse event support
- New config option, config.ssh.term to set terminal
### Changed
- Update to Xterm.js 2.4.0
- Minor code formatting cleanup
## 0.0.3 - [2017-02-16]
### Changed
- Update xterm to latest (2.3.0)
### Fixed
- Fixed misspelled config.ssh.port property
## 0.0.2 - [2017-02-01]
### Changed
- Moving terminal emulation to xterm.js
- updating module version dependencies
### Fixed
- Fixed issue with banners not being displayed properly from UNIX hosts when only lf is used
## 0.0.1 - [2016-06-28]
### Added
- Initial proof of concept and release. For historical purposes only.

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Bill Church
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

View file

@ -1,32 +1,32 @@
<!-- Version Version 0.2.12 - 2024-07-10T13:49:24.751Z - 533f719 -->
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>WebSSH2</title> <title>WebSSH2</title>
<style> <style>
html, body {background-color: #000;height: 100%;margin: 0;}.dropup-content {display: none;} html,
</style> body {
<link rel="stylesheet" href="/ssh/webssh2.css" /> background-color: #000;
<link rel="icon" href="/ssh/favicon.ico" /> height: 100%;
</head> margin: 0;
<body> }
<div class="box"> .dropup-content {
<div id="header"></div> display: none;
<div id="terminal-container" class="terminal"></div> }
<div id="bottomdiv"> </style>
<div class="dropup" id="menu"> <link href="/ssh/webssh2.css" rel="stylesheet"></head>
<i class="fas fa-bars fa-fw"></i> Menu <body>
<div id="dropupContent" class="dropup-content"> <div class="box">
<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a> <div id="header"></div>
<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a> <div id="terminal-container" class="terminal"></div>
<a id="reauthBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Switch User</a> <div id="bottomdiv">
<a id="credentialsBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Credentials</a> <div class="dropup" id="menu">
<i class="fas fa-bars fa-fw"></i> Menu
<div id="dropupContent" class="dropup-content"></div>
</div> </div>
<div id="footer"></div>
<div id="status"></div>
</div> </div>
<div id="footer"></div>
<div id="status"></div>
<div id="countdown"></div>
</div> </div>
</div> <script type="text/javascript" src="/ssh/webssh2.bundle.js"></script></body>
<script src="/ssh/webssh2.bundle.js" defer></script>
</body>
</html> </html>

File diff suppressed because one or more lines are too long

View file

@ -1,3 +1,4 @@
/*! Version 0.2.12 - 2024-07-10T13:49:24.747Z - 533f719 */
/** /**
* Copyright (c) 2014 The xterm.js authors. All rights reserved. * Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
@ -36,7 +37,7 @@
*/ */
.xterm { .xterm {
cursor: text; font-feature-settings: "liga" 0;
position: relative; position: relative;
user-select: none; user-select: none;
-ms-user-select: none; -ms-user-select: none;
@ -55,21 +56,21 @@
* The z-index of the helpers must be higher than the canvases in order for * The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top. * IMEs to appear on top.
*/ */
z-index: 5; z-index: 10;
} }
.xterm .xterm-helper-textarea { .xterm .xterm-helper-textarea {
padding: 0; /*
border: 0; * HACK: to fix IE's blinking cursor
margin: 0; * Move textarea out of the screen to the far left, so that the cursor is not visible.
/* Move textarea out of the screen to the far left, so that the cursor is not visible */ */
position: absolute; position: absolute;
opacity: 0; opacity: 0;
left: -9999em; left: -9999em;
top: 0; top: 0;
width: 0; width: 0;
height: 0; height: 0;
z-index: -5; z-index: -10;
/** Prevent wrapping so the IME appears against the textarea at the correct position */ /** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@ -125,13 +126,16 @@
line-height: normal; line-height: normal;
} }
.xterm {
cursor: text;
}
.xterm.enable-mouse-events { .xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default; cursor: default;
} }
.xterm.xterm-cursor-pointer, .xterm.xterm-cursor-pointer {
.xterm .xterm-cursor-pointer {
cursor: pointer; cursor: pointer;
} }
@ -140,25 +144,15 @@
cursor: crosshair; cursor: crosshair;
} }
.xterm .xterm-accessibility:not(.debug), .xterm .xterm-accessibility,
.xterm .xterm-message { .xterm .xterm-message {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
right: 0; right: 0;
z-index: 10; z-index: 100;
color: transparent; color: transparent;
pointer-events: none;
}
.xterm .xterm-accessibility-tree:not(.debug) *::selection {
color: transparent;
}
.xterm .xterm-accessibility-tree {
user-select: text;
white-space: pre;
} }
.xterm .live-region { .xterm .live-region {
@ -170,56 +164,16 @@
} }
.xterm-dim { .xterm-dim {
/* Dim should not apply to background, so the opacity of the foreground color is applied opacity: 0.5;
* explicitly in the generated class and reset to 1 here */
opacity: 1 !important;
} }
.xterm-underline-1 { text-decoration: underline; } .xterm-underline {
.xterm-underline-2 { text-decoration: double underline; } text-decoration: underline;
.xterm-underline-3 { text-decoration: wavy underline; }
.xterm-underline-4 { text-decoration: dotted underline; }
.xterm-underline-5 { text-decoration: dashed underline; }
.xterm-overline {
text-decoration: overline;
} }
.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}
.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
z-index: 7;
}
.xterm-decoration-overview-ruler {
z-index: 8;
position: absolute;
top: 0;
right: 0;
pointer-events: none;
}
.xterm-decoration-top {
z-index: 2;
position: relative;
}
body, html { body, html {
font-family: helvetica, sans-serif, arial; font-family: helvetica, sans-serif, arial;
font-size: 1em; font-size: 1em;
color: #111;
background-color: rgb(0, 0, 0); background-color: rgb(0, 0, 0);
color: rgb(240, 240, 240); color: rgb(240, 240, 240);
height: 100%; height: 100%;
@ -244,7 +198,7 @@ body, html {
} }
#terminal-container { #terminal-container {
display: block; display: block;
width: calc(100% - 1px); width: calc(100% - 1 px);
margin: 0 auto; margin: 0 auto;
padding: 2px; padding: 2px;
height: calc(100% - 19px); height: calc(100% - 19px);
@ -293,30 +247,6 @@ body, html {
text-align: left; text-align: left;
z-index: 100; z-index: 100;
} }
#countdown {
display: none;
color: rgb(240, 240, 240);
background-color: rgb(50, 50, 50);
padding-left: 10px;
padding-right: 10px;
border-color: white;
border-style: none solid none solid;
border-width: 1px;
text-align: left;
z-index: 100;
}
#countdown.active {
display: inline-block;
animation: countdown infinite alternate 200ms;
}
@keyframes countdown {
from {
background-color: rgb(255, 255, 0);
}
to {
background-color: inherit;
}
}
#menu { #menu {
display: inline-block; display: inline-block;
font-size: 16px; font-size: 16px;
@ -357,10 +287,9 @@ body, html {
.dropup:hover .dropup-content { .dropup:hover .dropup-content {
display: block; display: block;
} }
.dropup:active .dropup-content { .dropup:click .dropup-content {
display: block; display: block;
} }
.dropup:hover .dropbtn { .dropup:hover .dropbtn {
background-color: #3e8e41; background-color: #3e8e41;
} }

View file

@ -1,32 +1,32 @@
<!-- <%= htmlWebpackPlugin.options.version %> -->
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>WebSSH2</title> <title>WebSSH2</title>
<style> <style>
html, body {background-color: #000;height: 100%;margin: 0;}.dropup-content {display: none;} html,
</style> body {
<link rel="stylesheet" href="/ssh/webssh2.css" /> background-color: #000;
<link rel="icon" href="/ssh/favicon.ico" /> height: 100%;
</head> margin: 0;
<body> }
<div class="box"> .dropup-content {
<div id="header"></div> display: none;
<div id="terminal-container" class="terminal"></div> }
<div id="bottomdiv"> </style>
<div class="dropup" id="menu"> </head>
<i class="fas fa-bars fa-fw"></i> Menu <body>
<div id="dropupContent" class="dropup-content"> <div class="box">
<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a> <div id="header"></div>
<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a> <div id="terminal-container" class="terminal"></div>
<a id="reauthBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Switch User</a> <div id="bottomdiv">
<a id="credentialsBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Credentials</a> <div class="dropup" id="menu">
<i class="fas fa-bars fa-fw"></i> Menu
<div id="dropupContent" class="dropup-content"></div>
</div> </div>
<div id="footer"></div>
<div id="status"></div>
</div> </div>
<div id="footer"></div>
<div id="status"></div>
<div id="countdown"></div>
</div> </div>
</div> </body>
<script src="/ssh/webssh2.bundle.js" defer></script>
</body>
</html> </html>

View file

@ -1,6 +1,7 @@
body, html { body, html {
font-family: helvetica, sans-serif, arial; font-family: helvetica, sans-serif, arial;
font-size: 1em; font-size: 1em;
color: #111;
background-color: rgb(0, 0, 0); background-color: rgb(0, 0, 0);
color: rgb(240, 240, 240); color: rgb(240, 240, 240);
height: 100%; height: 100%;
@ -25,7 +26,7 @@ body, html {
} }
#terminal-container { #terminal-container {
display: block; display: block;
width: calc(100% - 1px); width: calc(100% - 1 px);
margin: 0 auto; margin: 0 auto;
padding: 2px; padding: 2px;
height: calc(100% - 19px); height: calc(100% - 19px);
@ -74,30 +75,6 @@ body, html {
text-align: left; text-align: left;
z-index: 100; z-index: 100;
} }
#countdown {
display: none;
color: rgb(240, 240, 240);
background-color: rgb(50, 50, 50);
padding-left: 10px;
padding-right: 10px;
border-color: white;
border-style: none solid none solid;
border-width: 1px;
text-align: left;
z-index: 100;
}
#countdown.active {
display: inline-block;
animation: countdown infinite alternate 200ms;
}
@keyframes countdown {
from {
background-color: rgb(255, 255, 0);
}
to {
background-color: inherit;
}
}
#menu { #menu {
display: inline-block; display: inline-block;
font-size: 16px; font-size: 16px;
@ -138,7 +115,7 @@ body, html {
.dropup:hover .dropup-content { .dropup:hover .dropup-content {
display: block; display: block;
} }
.dropup:active .dropup-content { .dropup:click .dropup-content {
display: block; display: block;
} }
.dropup:hover .dropbtn { .dropup:hover .dropbtn {

246
app/client/src/js/index.js Normal file
View file

@ -0,0 +1,246 @@
'use strict'
import * as io from 'socket.io-client'
import * as Terminal from 'xterm/dist/xterm'
import * as fit from 'xterm/dist/addons/fit/fit'
import { library, dom } from '@fortawesome/fontawesome-svg-core'
import { faBars, faClipboard, faDownload, faKey, faCog } from '@fortawesome/free-solid-svg-icons'
library.add(faBars, faClipboard, faDownload, faKey, faCog)
dom.watch()
require('xterm/dist/xterm.css')
require('../css/style.css')
Terminal.applyAddon(fit)
/* global Blob, logBtn, credentialsBtn, reauthBtn, downloadLogBtn */
var sessionLogEnable = false
var loggedData = false
var allowreplay = false
var allowreauth = false
var sessionLog, sessionFooter, logDate, currentDate, myFile, errorExists
var socket, termid // eslint-disable-line
var term = new Terminal()
// DOM properties
var status = document.getElementById('status')
var header = document.getElementById('header')
var dropupContent = document.getElementById('dropupContent')
var footer = document.getElementById('footer')
var terminalContainer = document.getElementById('terminal-container')
term.open(terminalContainer)
term.focus()
term.fit()
window.addEventListener('resize', resizeScreen, false)
function resizeScreen () {
term.fit()
socket.emit('resize', { cols: term.cols, rows: term.rows })
}
socket = io.connect({
path: '/ssh/socket.io'
})
term.on('data', function (data) {
socket.emit('data', data)
})
socket.on('data', function (data) {
term.write(data)
if (sessionLogEnable) {
sessionLog = sessionLog + data
}
})
socket.on('connect', function () {
socket.emit('geometry', term.cols, term.rows)
})
socket.on('setTerminalOpts', function (data) {
term.setOption('cursorBlink', data.cursorBlink)
term.setOption('scrollback', data.scrollback)
term.setOption('tabStopWidth', data.tabStopWidth)
term.setOption('bellStyle', data.bellStyle)
})
socket.on('title', function (data) {
document.title = data
})
socket.on('menu', function (data) {
drawMenu(data)
})
socket.on('status', function (data) {
status.innerHTML = data
})
socket.on('ssherror', function (data) {
status.innerHTML = data
status.style.backgroundColor = 'red'
errorExists = true
})
socket.on('headerBackground', function (data) {
header.style.backgroundColor = data
})
socket.on('header', function (data) {
if (data) {
header.innerHTML = data
header.style.display = 'block'
// header is 19px and footer is 19px, recaculate new terminal-container and resize
terminalContainer.style.height = 'calc(100% - 38px)'
resizeScreen()
}
})
socket.on('footer', function (data) {
sessionFooter = data
footer.innerHTML = data
})
socket.on('statusBackground', function (data) {
status.style.backgroundColor = data
})
socket.on('allowreplay', function (data) {
if (data === true) {
console.log('allowreplay: ' + data)
allowreplay = true
drawMenu(dropupContent.innerHTML + '<a id="credentialsBtn"><i class="fas fa-key fa-fw"></i> Credentials</a>')
} else {
allowreplay = false
console.log('allowreplay: ' + data)
}
})
socket.on('allowreauth', function (data) {
if (data === true) {
console.log('allowreauth: ' + data)
allowreauth = true
drawMenu(dropupContent.innerHTML + '<a id="reauthBtn"><i class="fas fa-key fa-fw"></i> Switch User</a>')
} else {
allowreauth = false
console.log('allowreauth: ' + data)
}
})
socket.on('disconnect', function (err) {
if (!errorExists) {
status.style.backgroundColor = 'red'
status.innerHTML =
'WEBSOCKET SERVER DISCONNECTED: ' + err
}
socket.io.reconnection(false)
})
socket.on('error', function (err) {
if (!errorExists) {
status.style.backgroundColor = 'red'
status.innerHTML = 'ERROR: ' + err
}
})
socket.on('reauth', function () {
(allowreauth) && reauthSession()
})
term.on('title', function (title) {
document.title = title
})
// draw/re-draw menu and reattach listeners
// when dom is changed, listeners are abandonded
function drawMenu (data) {
dropupContent.innerHTML = data
logBtn.addEventListener('click', toggleLog)
allowreauth && reauthBtn.addEventListener('click', reauthSession)
allowreplay && credentialsBtn.addEventListener('click', replayCredentials)
loggedData && downloadLogBtn.addEventListener('click', downloadLog)
}
// reauthenticate
function reauthSession () { // eslint-disable-line
console.log('re-authenticating')
window.location.href = '/ssh/reauth'
return false
}
// replay password to server, requires
function replayCredentials () { // eslint-disable-line
socket.emit('control', 'replayCredentials')
console.log('replaying credentials')
term.focus()
return false
}
// Set variable to toggle log data from client/server to a varialble
// for later download
function toggleLog () { // eslint-disable-line
if (sessionLogEnable === true) {
sessionLogEnable = false
loggedData = true
logBtn.innerHTML = '<i class="fas fa-clipboard fa-fw"></i> Start Log'
console.log('stopping log, ' + sessionLogEnable)
currentDate = new Date()
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' +
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n'
logDate = currentDate
term.focus()
return false
} else {
sessionLogEnable = true
loggedData = true
logBtn.innerHTML = '<i class="fas fa-cog fa-spin fa-fw"></i> Stop Log'
downloadLogBtn.style.color = '#000'
downloadLogBtn.addEventListener('click', downloadLog)
console.log('starting log, ' + sessionLogEnable)
currentDate = new Date()
sessionLog = 'Log Start for ' + sessionFooter + ': ' +
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n\r\n'
logDate = currentDate
term.focus()
return false
}
}
// cross browser method to "download" an element to the local system
// used for our client-side logging feature
function downloadLog () { // eslint-disable-line
if (loggedData === true) {
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) +
logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() +
logDate.getSeconds() + '.log'
// regex should eliminate escape sequences from being logged.
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><;]/g, '')], { // eslint-disable-line no-control-regex
type: 'text/plain'
})
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, myFile)
} else {
var elem = window.document.createElement('a')
elem.href = window.URL.createObjectURL(blob)
elem.download = myFile
document.body.appendChild(elem)
elem.click()
document.body.removeChild(elem)
}
}
term.focus()
}
// Add an event listener for capturing Ctrl + Shift + 6
document.addEventListener('keydown', function(event) {
if (event.ctrlKey && event.shiftKey && event.code === 'Digit6') {
// Prevent the default action
event.preventDefault();
// Emit the desired key sequence to the server
socket.emit('data', '\x1E'); // 0x1E is the RS control character
}
});

View file

@ -1,285 +0,0 @@
/* eslint-disable import/no-extraneous-dependencies */
import { io } from 'socket.io-client';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { library, dom } from '@fortawesome/fontawesome-svg-core';
import { faBars, faClipboard, faDownload, faKey, faCog } from '@fortawesome/free-solid-svg-icons';
library.add(faBars, faClipboard, faDownload, faKey, faCog);
dom.watch();
const debug = require('debug')('WebSSH2');
require('@xterm/xterm/css/xterm.css');
require('../css/style.css');
/* global Blob, logBtn, credentialsBtn, reauthBtn, downloadLogBtn */ // eslint-disable-line
let sessionLogEnable = false;
let loggedData = false;
let allowreplay = false;
let allowreauth = false;
let sessionLog: string;
let sessionFooter: any;
let logDate: {
getFullYear: () => any;
getMonth: () => number;
getDate: () => any;
getHours: () => any;
getMinutes: () => any;
getSeconds: () => any;
};
let currentDate: Date;
let myFile: string;
let errorExists: boolean;
const term = new Terminal();
// DOM properties
const logBtn = document.getElementById('logBtn');
const credentialsBtn = document.getElementById('credentialsBtn');
const reauthBtn = document.getElementById('reauthBtn');
const downloadLogBtn = document.getElementById('downloadLogBtn');
const status = document.getElementById('status');
const header = document.getElementById('header');
const footer = document.getElementById('footer');
const countdown = document.getElementById('countdown');
const fitAddon = new FitAddon();
const terminalContainer = document.getElementById('terminal-container');
term.loadAddon(fitAddon);
term.open(terminalContainer);
term.focus();
fitAddon.fit();
const socket = io({
path: '/ssh/socket.io',
});
// reauthenticate
function reauthSession () { // eslint-disable-line
debug('re-authenticating');
socket.emit('control', 'reauth');
window.location.href = '/ssh/reauth';
return false;
}
// cross browser method to "download" an element to the local system
// used for our client-side logging feature
function downloadLog () { // eslint-disable-line
if (loggedData === true) {
myFile = `WebSSH2-${logDate.getFullYear()}${
logDate.getMonth() + 1
}${logDate.getDate()}_${logDate.getHours()}${logDate.getMinutes()}${logDate.getSeconds()}.log`;
// regex should eliminate escape sequences from being logged.
const blob = new Blob(
[
sessionLog.replace(
// eslint-disable-next-line no-control-regex
/[\u001b\u009b][[\]()#;?]*(?:\d{1,4}(?:;\d{0,4})*)?[0-9A-ORZcf-nqry=><;]/g,
''
),
],
{
// eslint-disable-line no-control-regex
type: 'text/plain',
}
);
const elem = window.document.createElement('a');
elem.href = window.URL.createObjectURL(blob);
elem.download = myFile;
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
}
term.focus();
}
// Set variable to toggle log data from client/server to a varialble
// for later download
function toggleLog () { // eslint-disable-line
if (sessionLogEnable === true) {
sessionLogEnable = false;
loggedData = true;
logBtn.innerHTML = '<i class="fas fa-clipboard fa-fw"></i> Start Log';
currentDate = new Date();
sessionLog = `${sessionLog}\r\n\r\nLog End for ${sessionFooter}: ${currentDate.getFullYear()}/${
currentDate.getMonth() + 1
}/${currentDate.getDate()} @ ${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}\r\n`;
logDate = currentDate;
term.focus();
return false;
}
sessionLogEnable = true;
loggedData = true;
logBtn.innerHTML = '<i class="fas fa-cog fa-spin fa-fw"></i> Stop Log';
downloadLogBtn.style.color = '#000';
downloadLogBtn.addEventListener('click', downloadLog);
currentDate = new Date();
sessionLog = `Log Start for ${sessionFooter}: ${currentDate.getFullYear()}/${
currentDate.getMonth() + 1
}/${currentDate.getDate()} @ ${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}\r\n\r\n`;
logDate = currentDate;
term.focus();
return false;
}
// replay password to server, requires
function replayCredentials () { // eslint-disable-line
socket.emit('control', 'replayCredentials');
debug(`control: replayCredentials`);
term.focus();
return false;
}
// draw/re-draw menu and reattach listeners
// when dom is changed, listeners are abandonded
function drawMenu() {
logBtn.addEventListener('click', toggleLog);
if (allowreauth) {
reauthBtn.addEventListener('click', reauthSession);
reauthBtn.style.display = 'block';
}
if (allowreplay) {
credentialsBtn.addEventListener('click', replayCredentials);
credentialsBtn.style.display = 'block';
}
if (loggedData) {
downloadLogBtn.addEventListener('click', downloadLog);
downloadLogBtn.style.display = 'block';
}
}
function resizeScreen() {
fitAddon.fit();
socket.emit('resize', { cols: term.cols, rows: term.rows });
debug(`resize: ${JSON.stringify({ cols: term.cols, rows: term.rows })}`);
}
window.addEventListener('resize', resizeScreen, false);
term.onData((data) => {
socket.emit('data', data);
});
socket.on('data', (data: string | Uint8Array) => {
term.write(data);
if (sessionLogEnable) {
sessionLog += data;
}
});
socket.on('connect', () => {
socket.emit('geometry', term.cols, term.rows);
debug(`geometry: ${term.cols}, ${term.rows}`);
});
socket.on(
'setTerminalOpts',
(data: {
cursorBlink: boolean;
scrollback: number;
tabStopWidth: number;
bellStyle: 'none' | 'sound';
fontSize: number;
fontFamily: string;
letterSpacing: number;
lineHeight: number;
}) => {
term.options = data;
}
);
socket.on('title', (data: string) => {
document.title = data;
});
socket.on('menu', () => {
drawMenu();
});
socket.on('status', (data: string) => {
status.innerHTML = data;
});
socket.on('ssherror', (data: string) => {
status.innerHTML = data;
status.style.backgroundColor = 'red';
errorExists = true;
});
socket.on('headerBackground', (data: string) => {
header.style.backgroundColor = data;
});
socket.on('header', (data: string) => {
if (data) {
header.innerHTML = data;
header.style.display = 'block';
// header is 19px and footer is 19px, recaculate new terminal-container and resize
terminalContainer.style.height = 'calc(100% - 38px)';
resizeScreen();
}
});
socket.on('footer', (data: string) => {
sessionFooter = data;
footer.innerHTML = data;
});
socket.on('statusBackground', (data: string) => {
status.style.backgroundColor = data;
});
socket.on('allowreplay', (data: boolean) => {
if (data === true) {
debug(`allowreplay: ${data}`);
allowreplay = true;
drawMenu();
} else {
allowreplay = false;
debug(`allowreplay: ${data}`);
}
});
socket.on('allowreauth', (data: boolean) => {
if (data === true) {
debug(`allowreauth: ${data}`);
allowreauth = true;
drawMenu();
} else {
allowreauth = false;
debug(`allowreauth: ${data}`);
}
});
socket.on('disconnect', (err: any) => {
if (!errorExists) {
status.style.backgroundColor = 'red';
status.innerHTML = `WEBSOCKET SERVER DISCONNECTED: ${err}`;
}
socket.io.reconnection(false);
countdown.classList.remove('active');
});
socket.on('error', (err: any) => {
if (!errorExists) {
status.style.backgroundColor = 'red';
status.innerHTML = `ERROR: ${err}`;
}
});
socket.on('reauth', () => {
if (allowreauth) {
reauthSession();
}
});
// safe shutdown
let hasCountdownStarted = false;
socket.on('shutdownCountdownUpdate', (remainingSeconds: any) => {
if (!hasCountdownStarted) {
countdown.classList.add('active');
hasCountdownStarted = true;
}
countdown.innerText = `Shutting down in ${remainingSeconds}s`;
});
term.onTitleChange((title) => {
document.title = title;
});

View file

@ -1,9 +0,0 @@
{
"compilerOptions": {
"outDir": "./built",
"allowJs": true,
"target": "es6",
"moduleResolution": "node"
},
"include": ["./src/**/*"],
}

View file

@ -1,36 +1,28 @@
{ {
"listen": { "listen": {
"ip": "0.0.0.0", "ip": "0.0.0.0",
"port": 2224 "port": 2222
}, },
"socketio": { "http": {
"serveClient": false, "origins": ["*:*"]
"path": "/ssh/socket.io",
"origins": ["localhost:2222"],
}, },
"user": { "user": {
"name": null, "name": null,
"password": null, "password": null
"privatekey": null,
"overridebasic": false
}, },
"ssh": { "ssh": {
"host": null, "host": null,
"port": 22, "port": 22,
"localAddress": null,
"localPort": null,
"term": "xterm-color", "term": "xterm-color",
"readyTimeout": 20000, "readyTimeout": 20000,
"keepaliveInterval": 120000, "keepaliveInterval": 120000,
"keepaliveCountMax": 10, "keepaliveCountMax": 10
"allowedSubnets": []
}, },
"terminal": { "terminal": {
"cursorBlink": true, "cursorBlink": true,
"scrollback": 10000, "scrollback": 10000,
"tabStopWidth": 8, "tabStopWidth": 8,
"bellStyle": "sound", "bellStyle": "sound"
"fontSize": 14
}, },
"header": { "header": {
"text": null, "text": null,
@ -78,6 +70,5 @@
"server": false "server": false
}, },
"accesslog": false, "accesslog": false,
"verify": false, "verify": false
"safeShutdownDuration": 300
} }

View file

@ -1,32 +1,29 @@
/* eslint no-console: ["error", { allow: ["warn", "error"] }] */ 'use strict'
/* jshint esversion: 6, asi: true, node: true */ /* jshint esversion: 6, asi: true, node: true */
/* /*
* index.js * index.js
* *
* WebSSH2 - Web to SSH2 gateway * WebSSH2 - Web to SSH2 gateway
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017 * Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
* See LICENSE file
* *
* test change
*/ */
const { config } = require('./server/app'); var config = require('./server/app').config
const { server } = require('./server/app'); var server = require('./server/app').server
server.listen({ host: config.listen.ip, port: config.listen.port }); server.listen({ host: config.listen.ip, port: config.listen.port
})
// eslint-disable-next-line no-console console.log('WebSSH2 service listening on ' + config.listen.ip + ':' + config.listen.port)
console.log(`WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}`);
server.on('error', (err) => { server.on('error', function (err) {
if (err.code === 'EADDRINUSE') { if (err.code === 'EADDRINUSE') {
config.listen.port += 1; config.listen.port++
console.warn(`WebSSH2 Address in use, retrying on port ${config.listen.port}`); console.warn('WebSSH2 Address in use, retrying on port ' + config.listen.port)
setTimeout(() => { setTimeout(function () {
server.listen(config.listen.port); server.listen(config.listen.port)
}, 250); }, 250)
} else { } else {
// eslint-disable-next-line no-console console.log('WebSSH2 server.listen ERROR: ' + err.code)
console.log(`WebSSH2 server.listen ERROR: ${err.code}`);
} }
}); })

View file

@ -1,17 +0,0 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Node",
"target": "ES2020",
"jsx": "preserve",
"strictFunctionTypes": true
},
"exclude": [
"node_modules",
"**/node_modules/*"
],
"include": [
"server/*.js",
"index.js"
]
}

11335
app/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "webssh2", "name": "webssh2",
"version": "0.6.0-pre-1", "version": "0.2.13",
"ignore": [ "ignore": [
".gitignore" ".gitignore"
], ],
@ -26,64 +26,67 @@
} }
], ],
"engines": { "engines": {
"node": ">= 14" "node": ">= 6"
}, },
"bugs": { "bugs": {
"url": "https://github.com/billchurch/WebSSH2/issues" "url": "https://github.com/billchurch/WebSSH2/issues"
}, },
"dependencies": { "dependencies": {
"basic-auth": "~2.0.1", "basic-auth": "~2.0.1",
"cidr-matcher": "^2.1.1", "colors": "~1.3.2",
"debug": "^4.3.4", "compression": "~1.7.3",
"express": "^4.19.2", "debug": "~4.1.0",
"express-session": "^1.18.0", "express": "~4.16.4",
"morgan": "~1.10.0", "express-session": "~1.15.6",
"read-config-ng": "^3.0.7", "morgan": "~1.9.1",
"serve-favicon": "^2.5.0", "read-config-ng": "~3.0.7",
"socket.io": "^4.7.5", "serve-favicon": "~2.5.0",
"ssh2": "^1.15.0", "socket.io": "~2.2.0",
"validator": "^13.11.0", "ssh2": "~0.8.9",
"winston": "^3.13.0" "validator": "~10.9.0"
}, },
"scripts": { "scripts": {
"start": "node index.js", "start": "node index.js",
"build": "webpack --progress --config scripts/webpack.prod.js", "build": "webpack --progress --colors --config scripts/webpack.prod.js",
"builddev": "webpack --progress --config scripts/webpack.dev.js", "builddev": "webpack --progress --colors --config scripts/webpack.dev.js",
"analyze": "webpack --json --config scripts/webpack.prod.js | webpack-bundle-size-analyzer", "analyze": "webpack --json --config scripts/webpack.prod.js | webpack-bundle-size-analyzer",
"test": "snyk test", "test": "snyk test",
"watch": "nodemon index.js", "watch": "nodemon index.js",
"cleanmac": "find . -name '.DS_Store' -type f -delete", "standard": "standard --verbose --fix | snazzy",
"release": "standard-version" "cleanmac": "find . -name '.DS_Store' -type f -delete"
},
"standard": {
"ignore": [
"client/public/webssh2.bundle.js",
"bigip/*",
"screenshots/*",
"bin/*",
"build/*",
"workspace/*"
]
}, },
"devDependencies": { "devDependencies": {
"@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/fontawesome-svg-core": "^1.2.12",
"@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^5.6.3",
"@typescript-eslint/eslint-plugin": "^7.7.1", "clean-webpack-plugin": "^1.0.0",
"@typescript-eslint/parser": "^7.7.1", "copy-webpack-plugin": "^4.6.0",
"@xterm/addon-fit": "^0.10.0", "cross-env": "^5.2.0",
"@xterm/xterm": "^5.5.0", "css-loader": "^2.1.0",
"clean-webpack-plugin": "^4.0.0", "extract-text-webpack-plugin": "^4.0.0-beta.0",
"copy-webpack-plugin": "^12.0.2", "file-loader": "^3.0.1",
"css-loader": "^7.1.1", "html-webpack-plugin": "^3.2.0",
"eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"mini-css-extract-plugin": "^2.9.0",
"nodaemon": "0.0.5", "nodaemon": "0.0.5",
"npm-check-updates": "^16.14.20", "postcss-discard-comments": "^4.0.1",
"prettier": "^3.2.5", "snazzy": "^8.0.0",
"snazzy": "^9.0.0", "standard": "^12.0.1",
"snyk": "^1.1290.0", "style-loader": "^0.23.1",
"socket.io-client": "^4.7.5", "uglifyjs-webpack-plugin": "^2.1.1",
"source-map-loader": "^5.0.0", "url-loader": "^1.1.2",
"standard-version": "^9.5.0", "webpack": "^4.28.4",
"terser-webpack-plugin": "^5.3.10", "webpack-cli": "^3.2.1",
"ts-loader": "^9.5.1", "webpack-merge": "^4.2.1",
"typescript": "^5.4.5", "webpack-stream": "^5.2.1",
"webpack": "^5.91.0", "xterm": "^3.10.1",
"webpack-cli": "^5.1.4", "zip-webpack-plugin": "^4.0.1"
"webpack-merge": "^5.10.0"
} }
} }

View file

@ -1,44 +1,60 @@
/* eslint-disable import/no-extraneous-dependencies */ const webpack = require("webpack");
const path = require('path'); const { BannerPlugin } = require("webpack");
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require("path");
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CopyWebpackPlugin = require("copy-webpack-plugin");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const packageJson = require("../package.json"); // Load package.json
const commitHash = require("child_process")
.execSync("git rev-parse --short HEAD")
.toString()
.trim();
module.exports = { module.exports = {
context: path.resolve('__dirname', '../'), context: path.resolve(__dirname, "../"),
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'],
},
entry: { entry: {
webssh2: './client/src/js/index.ts', webssh2: "./client/src/js/index.js",
}, },
plugins: [ plugins: [
new CleanWebpackPlugin(), new BannerPlugin({
new CopyWebpackPlugin({ banner: `Version ${
patterns: ['./client/src/client.htm', './client/src/favicon.ico'], packageJson.version
} - ${new Date().toISOString()} - ${commitHash}`,
include: /\.(js|css|html|htm)$/,
}), }),
new MiniCssExtractPlugin(), new CleanWebpackPlugin(["client/public"], {
root: path.resolve("__dirname", "../"),
verbose: true,
}),
new HtmlWebpackPlugin({
template: "./client/src/client.htm", // Path to your source template
filename: "client.htm", // Optional: output file name, defaults to index.html
minify: false,
scriptLoading: "defer",
version: `Version ${
packageJson.version
} - ${new Date().toISOString()} - ${commitHash}`,
publicPath: "/ssh/", // Prepend /ssh/ to the script tags
}),
new CopyWebpackPlugin([
{ from: "./client/src/favicon.ico", to: "favicon.ico" },
]),
new ExtractTextPlugin("[name].css"),
], ],
output: { output: {
filename: '[name].bundle.js', filename: "[name].bundle.js",
path: path.resolve(__dirname, '../client/public'), path: path.resolve(__dirname, "../client/public"),
publicPath: "/ssh/", // Prepend /ssh/ to the script tags
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.css$/, test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'], use: ExtractTextPlugin.extract({
}, fallback: "style-loader",
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'. use: [{ loader: "css-loader" }],
{ }),
test: /\.tsx?$/,
loader: 'ts-loader',
},
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{
test: /\.js$/,
loader: 'source-map-loader',
}, },
], ],
}, },

View file

@ -1,10 +1,9 @@
/* eslint-disable import/no-extraneous-dependencies */ const merge = require('webpack-merge')
const merge = require('webpack-merge'); const common = require('./webpack.common.js')
const common = require('./webpack.common.js');
module.exports = merge(common, { module.exports = merge(common, {
devtool: 'inline-source-map', devtool: 'inline-source-map',
devServer: { devServer: {
contentBase: '../client/public', contentBase: '../client/public'
}, }
}); })

View file

@ -1,19 +1,21 @@
/* eslint-disable import/no-extraneous-dependencies */ const merge = require("webpack-merge");
const TerserPlugin = require('terser-webpack-plugin'); const UglifyJSPlugin = require("uglifyjs-webpack-plugin");
const { merge } = require('webpack-merge'); const common = require("./webpack.common.js");
const common = require('./webpack.common.js');
module.exports = merge(common, { module.exports = merge(
mode: 'production', {
optimization: { plugins: [
minimize: true, new UglifyJSPlugin({
minimizer: [ uglifyOptions: {
new TerserPlugin({
terserOptions: {
ie8: false, ie8: false,
safari10: false, dead_code: true,
output: {
comments: false,
beautify: false,
},
}, },
}), }),
], ],
}, },
}); common
);

View file

@ -1,111 +1,242 @@
'use strict'
/* jshint esversion: 6, asi: true, node: true */ /* jshint esversion: 6, asi: true, node: true */
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }],
no-console: ["error", { allow: ["warn", "error", "info"] }] */
// app.js // app.js
// eslint-disable-next-line import/order var path = require('path')
const config = require('./config'); var fs = require('fs')
const path = require('path'); var nodeRoot = path.dirname(require.main.filename)
var configPath = path.join(nodeRoot, 'config.json')
var publicPath = path.join(nodeRoot, 'client', 'public')
console.log('WebSSH2 service reading config from: ' + configPath)
var express = require('express')
var logger = require('morgan')
const nodeRoot = path.dirname(require.main.filename); // sane defaults if config.json or parts are missing
const publicPath = path.join(nodeRoot, 'client', 'public'); let config = {
const express = require('express'); listen: {
const logger = require('morgan'); ip: '0.0.0.0',
port: 2222
const app = express(); },
const server = require('http').createServer(app); http: {
const favicon = require('serve-favicon'); origins: ['*:*']
const io = require('socket.io')(server, config.socketio); },
const session = require('express-session')(config.express); user: {
name: null,
const appSocket = require('./socket'); password: null
const { setDefaultCredentials, basicAuth } = require('./util'); },
const { webssh2debug } = require('./logging'); ssh: {
const { reauth, connect, notfound, handleErrors } = require('./routes'); host: null,
port: 22,
setDefaultCredentials(config.user); term: 'xterm-color',
readyTimeout: 20000,
// safe shutdown keepaliveInterval: 120000,
let remainingSeconds = config.safeShutdownDuration; keepaliveCountMax: 10
let shutdownMode = false; },
let shutdownInterval; terminal: {
let connectionCount = 0; cursorBlink: true,
// eslint-disable-next-line consistent-return scrollback: 10000,
function safeShutdownGuard(req, res, next) { tabStopWidth: 8,
if (!shutdownMode) return next(); bellStyle: 'sound'
res.status(503).end('Service unavailable: Server shutting down'); },
header: {
text: null,
background: 'green'
},
session: {
name: 'WebSSH2',
secret: 'mysecret'
},
options: {
challengeButton: true,
allowreauth: true
},
algorithms: {
kex: [
'ecdh-sha2-nistp256',
'ecdh-sha2-nistp384',
'ecdh-sha2-nistp521',
'diffie-hellman-group-exchange-sha256',
'diffie-hellman-group14-sha1'
],
cipher: [
'aes128-ctr',
'aes192-ctr',
'aes256-ctr',
'aes128-gcm',
'aes128-gcm@openssh.com',
'aes256-gcm',
'aes256-gcm@openssh.com',
'aes256-cbc'
],
hmac: ['hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'],
compress: ['none', 'zlib@openssh.com', 'zlib']
},
serverlog: {
client: false,
server: false
},
accesslog: false,
verify: false
} }
// test if config.json exists, if not provide error message but try to run
// anyway
try {
if (fs.existsSync(configPath)) {
console.log('ephemeral_auth service reading config from: ' + configPath)
config = require('read-config-ng')(configPath)
} else {
console.error(
'\n\nERROR: Missing config.json for webssh. Current config: ' +
JSON.stringify(config)
)
console.error('\n See config.json.sample for details\n\n')
}
} catch (err) {
console.error(
'\n\nERROR: Missing config.json for webssh. Current config: ' +
JSON.stringify(config)
)
console.error('\n See config.json.sample for details\n\n')
console.error('ERROR:\n\n ' + err)
}
var session = require('express-session')({
secret: config.session.secret,
name: config.session.name,
resave: true,
saveUninitialized: false,
unset: 'destroy'
})
var app = express()
var compression = require('compression')
var server = require('http').Server(app)
var myutil = require('./util')
var validator = require('validator')
var io = require('socket.io')(server, {
serveClient: false,
path: '/ssh/socket.io',
origins: config.http.origins
})
var socket = require('./socket')
var expressOptions = require('./expressOptions')
var favicon = require('serve-favicon')
// express // express
app.use(safeShutdownGuard); app.use(compression({ level: 9 }))
app.use(session); app.use(session)
if (config.accesslog) app.use(logger('common')); app.use(myutil.basicAuth)
app.disable('x-powered-by'); if (config.accesslog) app.use(logger('common'))
app.use(favicon(path.join(publicPath, 'favicon.ico'))); app.disable('x-powered-by')
app.use(express.urlencoded({ extended: true }));
app.post('/ssh/host/:host?', connect);
app.post('/ssh', express.static(publicPath, config.express.ssh));
app.use('/ssh', express.static(publicPath, config.express.ssh));
app.use(basicAuth);
app.get('/ssh/reauth', reauth);
app.get('/ssh/host/:host?', connect);
app.use(notfound);
app.use(handleErrors);
// clean stop // static files
function stopApp(reason) { app.use('/ssh', express.static(publicPath, expressOptions))
shutdownMode = false; // app.use(express.static(publicPath, expressOptions))
if (reason) console.info(`Stopping: ${reason}`);
clearInterval(shutdownInterval);
io.close();
server.close();
}
// bring up socket // favicon from root if being pre-fetched by browser to prevent a 404
io.on('connection', appSocket); app.use(favicon(path.join(publicPath, 'favicon.ico')))
app.get('/ssh/reauth', function (req, res, next) {
var r = req.headers.referer || '/'
res
.status(401)
.send(
'<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=' +
r +
'"></head><body bgcolor="#000"></body></html>'
)
})
// eslint-disable-next-line complexity
app.get('/ssh/host/:host?', function (req, res, next) {
res.sendFile(path.join(path.join(publicPath, 'client.htm')))
// capture, assign, and validated variables
req.session.ssh = {
host:
(validator.isIP(req.params.host + '') && req.params.host) ||
(validator.isFQDN(req.params.host) && req.params.host) ||
(/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.params.host) &&
req.params.host) ||
config.ssh.host,
port:
(validator.isInt(req.query.port + '', { min: 1, max: 65535 }) &&
req.query.port) ||
config.ssh.port,
header: {
name: req.query.header || config.header.text,
background: req.query.headerBackground || config.header.background
},
algorithms: config.algorithms,
keepaliveInterval: config.ssh.keepaliveInterval,
keepaliveCountMax: config.ssh.keepaliveCountMax,
term:
(/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) &&
req.query.sshterm) ||
config.ssh.term,
terminal: {
cursorBlink: validator.isBoolean(req.query.cursorBlink + '')
? myutil.parseBool(req.query.cursorBlink)
: config.terminal.cursorBlink,
scrollback:
validator.isInt(req.query.scrollback + '', { min: 1, max: 200000 }) &&
req.query.scrollback
? req.query.scrollback
: config.terminal.scrollback,
tabStopWidth:
validator.isInt(req.query.tabStopWidth + '', { min: 1, max: 100 }) &&
req.query.tabStopWidth
? req.query.tabStopWidth
: config.terminal.tabStopWidth,
bellStyle:
req.query.bellStyle &&
['sound', 'none'].indexOf(req.query.bellStyle) > -1
? req.query.bellStyle
: config.terminal.bellStyle
},
allowreplay:
config.options.challengeButton ||
(validator.isBoolean(req.headers.allowreplay + '')
? myutil.parseBool(req.headers.allowreplay)
: false),
allowreauth: config.options.allowreauth || false,
mrhsession:
validator.isAlphanumeric(req.headers.mrhsession + '') &&
req.headers.mrhsession
? req.headers.mrhsession
: 'none',
serverlog: {
client: config.serverlog.client || false,
server: config.serverlog.server || false
},
readyTimeout:
(validator.isInt(req.query.readyTimeout + '', { min: 1, max: 300000 }) &&
req.query.readyTimeout) ||
config.ssh.readyTimeout
}
if (req.session.ssh.header.name) validator.escape(req.session.ssh.header.name)
if (req.session.ssh.header.background)
validator.escape(req.session.ssh.header.background)
})
// express error handling
app.use(function (req, res, next) {
res.status(404).send("Sorry can't find that!")
})
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
// socket.io // socket.io
// expose express session with socket.request.session // expose express session with socket.request.session
io.use((socket, next) => { io.use(function (socket, next) {
socket.request.res ? session(socket.request, socket.request.res, next) : next(next); // eslint disable-line socket.request.res
}); ? session(socket.request, socket.request.res, next)
: next(next)
})
function countdownTimer() { // bring up socket
if (!shutdownMode) clearInterval(shutdownInterval); io.on('connection', socket)
remainingSeconds -= 1;
if (remainingSeconds <= 0) {
stopApp('Countdown is over');
} else io.emit('shutdownCountdownUpdate', remainingSeconds);
}
const signals = ['SIGTERM', 'SIGINT']; module.exports = { server: server, config: config }
signals.forEach((signal) =>
process.on(signal, () => {
if (shutdownMode) stopApp('Safe shutdown aborted, force quitting');
if (!connectionCount > 0) stopApp('All connections ended');
shutdownMode = true;
console.error(
`\r\n${connectionCount} client(s) are still connected.\r\nStarting a ${remainingSeconds} seconds countdown.\r\nPress Ctrl+C again to force quit`
);
if (!shutdownInterval) shutdownInterval = setInterval(countdownTimer, 1000);
})
);
module.exports = { server, config };
const onConnection = (socket) => {
connectionCount += 1;
socket.on('disconnect', () => {
connectionCount -= 1;
if (connectionCount <= 0 && shutdownMode) {
stopApp('All clients disconnected');
}
});
socket.on('geometry', (cols, rows) => {
// TODO need to rework how we pass settings to ssh2, this is less than ideal
socket.request.session.ssh.cols = cols;
socket.request.session.ssh.rows = rows;
webssh2debug(socket, `SOCKET GEOMETRY: termCols = ${cols}, termRows = ${rows}`);
});
};
io.on('connection', onConnection);

View file

@ -1,136 +0,0 @@
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }],
no-console: ["error", { allow: ["warn", "error", "info"] }] */
const fs = require('fs');
const path = require('path');
const debugWebSSH2 = require('debug')('WebSSH2');
const crypto = require('crypto');
const util = require('util');
const readconfig = require('read-config-ng');
const nodeRoot = path.dirname(require.main.filename);
const configPath = path.join(nodeRoot, 'config.json');
let myConfig;
// establish defaults
const configDefault = {
listen: {
ip: '0.0.0.0',
port: 2222,
},
socketio: {
serveClient: false,
path: '/ssh/socket.io',
origins: ['localhost:2222'],
},
express: {
secret: crypto.randomBytes(20).toString('hex'),
name: 'WebSSH2',
resave: true,
saveUninitialized: false,
unset: 'destroy',
ssh: {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1s',
redirect: false,
setHeaders(res) {
res.set('x-timestamp', Date.now());
},
},
},
user: {
name: null,
password: null,
privatekey: null,
overridebasic: false,
},
ssh: {
host: null,
port: 22,
term: 'xterm-color',
readyTimeout: 20000,
keepaliveInterval: 120000,
keepaliveCountMax: 10,
allowedSubnets: [],
},
terminal: {
cursorBlink: true,
scrollback: 10000,
tabStopWidth: 8,
bellStyle: 'sound',
},
header: {
text: null,
background: 'green',
},
options: {
challengeButton: true,
allowreauth: true,
},
algorithms: {
kex: [
'ecdh-sha2-nistp256',
'ecdh-sha2-nistp384',
'ecdh-sha2-nistp521',
'diffie-hellman-group-exchange-sha256',
'diffie-hellman-group14-sha1',
],
cipher: [
'aes128-ctr',
'aes192-ctr',
'aes256-ctr',
'aes128-gcm',
'aes128-gcm@openssh.com',
'aes256-gcm',
'aes256-gcm@openssh.com',
'aes256-cbc',
],
hmac: ['hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'],
compress: ['none', 'zlib@openssh.com', 'zlib'],
},
serverlog: {
client: false,
server: false,
},
accesslog: false,
verify: false,
safeShutdownDuration: 300,
};
// test if config.json exists, if not provide error message but try to run anyway
try {
if (!fs.existsSync(configPath)) {
console.error(
`\n\nERROR: Missing config.json for WebSSH2. Current config: ${util.inspect(myConfig)}`
);
console.error('\n See config.json.sample for details\n\n');
}
console.info(`WebSSH2 service reading config from: ${configPath}`);
const configFile = readconfig(configPath, { override: true });
// myConfig = merger.mergeObjects([configDefault, configFile]);
myConfig = { ...configDefault, ...configFile };
debugWebSSH2(`\nCurrent config: ${util.inspect(myConfig)}`);
} catch (err) {
myConfig = configDefault;
console.error(
`\n\nERROR: Missing config.json for WebSSH2. Current config: ${util.inspect(myConfig)}`
);
console.error('\n See config.json.sample for details\n\n');
console.error(`ERROR:\n\n ${err}`);
}
const config = myConfig;
if (process.env.LISTEN) config.listen.ip = process.env.LISTEN;
if (process.env.PORT) config.listen.port = process.env.PORT;
if (process.env.SOCKETIO_ORIGINS) config.socketio.origins = process.env.SOCKETIO_ORIGINS;
if (process.env.SOCKETIO_PATH) config.socketio.path = process.env.SOCKETIO_PATH;
if (process.env.SOCKETIO_SERVECLIENT)
config.socketio.serveClient = process.env.SOCKETIO_SERVECLIENT;
module.exports = config;

View file

@ -0,0 +1,11 @@
module.exports = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1s',
redirect: false,
setHeaders: function (res, path, stat) {
res.set('x-timestamp', Date.now())
}
}

View file

@ -1,36 +0,0 @@
<head><title>Post Test</title></head>
<html>
<body>
<h1>Credentials over HTTP POST test</h1>
<p>This is a test to demonstrate sending credentials over POST instead of requiring HTTP Basic. If you use this, be sure to secure the app/site with HTTPS!</p>
<form method="POST" action="http://localhost:2222/ssh/host/192.168.0.1">
<p>
<label for="username">Username</label>
<input name="username">
</p>
<label for="userpassword">Password</label>
<input name="userpassword" type="password">
</p>
<p>
<label for="header">Header text</label>
<input name="header" value="This is a test">
<p>
<label for="headerBackground">Header Background Color</label>
<input name="headerBackground" value="red">
</p>
<fieldset>
<legend>Cursor Blink:</legend>
<div>
<input type="radio" id="false" name="cursorBlink" value="false"
checked>
<label for="false">False</label>
</div>
<div>
<input type="radio" id="true" name="cursorBlink" value="true">
<label for="true">True</label>
</div>
</fieldset>
<button>Login</button>
</form>
</body>
</html>

View file

@ -1,47 +0,0 @@
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }],
no-console: ["error", { allow: ["warn", "error", "info"] }] */
/* jshint esversion: 6, asi: true, node: true */
// logging.js
// private
const debug = require('debug');
const util = require('util');
/**
* generate consistent prefix for log messages
* with epress session id and socket session id
* @param {object} socket Socket information
*/
function prefix(socket) {
return `(${socket.request.sessionID}/${socket.id})`;
}
// public
function webssh2debug(socket, msg) {
debug('WebSSH2')(`${prefix(socket)} ${msg}`);
}
/**
* audit log to console
* @param {object} socket Socket information
* @param {object} msg log message
*/
function auditLog(socket, msg) {
console.info(`WebSSH2 ${prefix(socket)} AUDIT: ${msg}`);
}
/**
* logs error to socket client (if connected)
* and console.
* @param {object} socket Socket information
* @param {string} myFunc Function calling this function
* @param {object} err error object or error message
*/
function logError(socket, myFunc, err) {
console.error(`WebSSH2 ${prefix(socket)} ERROR: ${myFunc}: ${err}`);
webssh2debug(socket, `logError: ${myFunc}: ${util.inspect(err)}`);
if (!socket.request.session) return;
socket.emit('ssherror', `SSH ${myFunc}: ${err}`);
}
module.exports = { logError, auditLog, webssh2debug };

View file

@ -1,203 +0,0 @@
/* eslint-disable no-console */
// ssh.js
const validator = require('validator');
const path = require('path');
const nodeRoot = path.dirname(require.main.filename);
const publicPath = path.join(nodeRoot, 'client', 'public');
const { parseBool } = require('./util');
const config = require('./config');
exports.reauth = function reauth(req, res) {
let { referer } = req.headers;
if (!validator.isURL(referer, { host_whitelist: ['localhost'] })) {
console.error(
`WebSSH2 (${req.sessionID}) ERROR: Referrer '${referer}' for '/reauth' invalid. Setting to '/' which will probably fail.`
);
referer = '/';
}
res
.status(401)
.send(
`<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${referer}"></head><body bgcolor="#000"></body></html>`
);
};
exports.connect = function connect(req, res) {
res.sendFile(path.join(path.join(publicPath, 'client.htm')));
let { host, port } = config.ssh;
let { text: header, background: headerBackground } = config.header;
let { term: sshterm, readyTimeout } = config.ssh;
let {
cursorBlink,
scrollback,
tabStopWidth,
bellStyle,
fontSize,
fontFamily,
letterSpacing,
lineHeight,
} = config.terminal;
// capture, assign, and validate variables
if (req.params?.host) {
if (
validator.isIP(`${req.params.host}`) ||
validator.isFQDN(req.params.host) ||
/^(([a-z]|[A-Z]|\d|[!^(){}\-_~])+)?\w$/.test(req.params.host)
) {
host = req.params.host;
}
}
if (req.method === 'POST' && req.body.username && req.body.userpassword) {
req.session.username = req.body.username;
req.session.userpassword = req.body.userpassword;
if (req.body.port && validator.isInt(`${req.body.port}`, { min: 1, max: 65535 }))
port = req.body.port;
if (req.body.header) header = req.body.header;
if (req.body.headerBackground) {
headerBackground = req.body.headerBackground;
console.log(`background: ${req.body.headerBackground}`);
}
if (req.body.sshterm && /^(([a-z]|[A-Z]|\d|[!^(){}\-_~])+)?\w$/.test(req.body.sshterm))
sshterm = req.body.sshterm;
if (req.body.cursorBlink && validator.isBoolean(`${req.body.cursorBlink}`))
cursorBlink = parseBool(req.body.cursorBlink);
if (req.body.scrollback && validator.isInt(`${req.body.scrollback}`, { min: 1, max: 200000 }))
scrollback = req.body.scrollback;
if (req.body.tabStopWidth && validator.isInt(`${req.body.tabStopWidth}`, { min: 1, max: 100 }))
tabStopWidth = req.body.tabStopWidth;
if (req.body.bellStyle && ['sound', 'none'].indexOf(req.body.bellStyle) > -1)
bellStyle = req.body.bellStyle;
if (
req.body.readyTimeout &&
validator.isInt(`${req.body.readyTimeout}`, { min: 1, max: 300000 })
)
readyTimeout = req.body.readyTimeout;
if (req.body.fontSize && validator.isNumeric(`${req.body.fontSize}`))
fontSize = req.body.fontSize;
if (req.body.fontFamily) fontFamily = req.body.fontFamily;
if (req.body.letterSpacing && validator.isNumeric(`${req.body.letterSpacing}`))
letterSpacing = req.body.letterSpacing;
if (req.body.lineHeight && validator.isNumeric(`${req.body.lineHeight}`))
lineHeight = req.body.lineHeight;
}
if (req.method === 'GET') {
if (req.query?.port && validator.isInt(`${req.query.port}`, { min: 1, max: 65535 }))
port = req.query.port;
if (req.query?.header) header = req.query.header;
if (req.query?.headerBackground) headerBackground = req.query.headerBackground;
if (req.query?.sshterm && /^(([a-z]|[A-Z]|\d|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm))
sshterm = req.query.sshterm;
if (req.query?.cursorBlink && validator.isBoolean(`${req.query.cursorBlink}`))
cursorBlink = parseBool(req.query.cursorBlink);
if (
req.query?.scrollback &&
validator.isInt(`${req.query.scrollback}`, { min: 1, max: 200000 })
)
scrollback = req.query.scrollback;
if (
req.query?.tabStopWidth &&
validator.isInt(`${req.query.tabStopWidth}`, { min: 1, max: 100 })
)
tabStopWidth = req.query.tabStopWidth;
if (req.query?.bellStyle && ['sound', 'none'].indexOf(req.query.bellStyle) > -1)
bellStyle = req.query.bellStyle;
if (
req.query?.readyTimeout &&
validator.isInt(`${req.query.readyTimeout}`, { min: 1, max: 300000 })
)
readyTimeout = req.query.readyTimeout;
if (req.query?.fontSize && validator.isNumeric(`${req.query.fontSize}`))
fontSize = req.query.fontSize;
if (req.query?.fontFamily) fontFamily = req.query.fontFamily;
if (req.query?.lineHeight && validator.isNumeric(`${req.query.lineHeight}`))
lineHeight = req.query.lineHeight;
if (req.query?.letterSpacing && validator.isNumeric(`${req.query.letterSpacing}`))
letterSpacing = req.query.letterSpacing;
}
req.session.ssh = {
host,
port,
localAddress: config.ssh.localAddress,
localPort: config.ssh.localPort,
header: {
name: header,
background: headerBackground,
},
algorithms: config.algorithms,
keepaliveInterval: config.ssh.keepaliveInterval,
keepaliveCountMax: config.ssh.keepaliveCountMax,
allowedSubnets: config.ssh.allowedSubnets,
term: sshterm,
terminal: {
cursorBlink,
scrollback,
tabStopWidth,
bellStyle,
fontSize,
fontFamily,
letterSpacing,
lineHeight,
},
cols: null,
rows: null,
allowreplay:
config.options.challengeButton ||
(validator.isBoolean(`${req.headers.allowreplay}`)
? parseBool(req.headers.allowreplay)
: false),
allowreauth: config.options.allowreauth || false,
mrhsession:
validator.isAlphanumeric(`${req.headers.mrhsession}`) && req.headers.mrhsession
? req.headers.mrhsession
: 'none',
serverlog: {
client: config.serverlog.client || false,
server: config.serverlog.server || false,
},
readyTimeout,
};
if (req.session.ssh.header.name) validator.escape(req.session.ssh.header.name);
if (req.session.ssh.header.background) validator.escape(req.session.ssh.header.background);
};
exports.notfound = function notfound(_req, res) {
res.status(404).send("Sorry, can't find that!");
};
exports.handleErrors = function handleErrors(err, _req, res) {
console.error(err.stack);
res.status(500).send('Something broke!');
};

View file

@ -1,241 +1,173 @@
/* eslint-disable complexity */ 'use strict'
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }],
no-console: ["error", { allow: ["warn", "error"] }] */
/* jshint esversion: 6, asi: true, node: true */ /* jshint esversion: 6, asi: true, node: true */
// socket.js // socket.js
// private // private
const debug = require('debug'); var debug = require('debug')
const SSH = require('ssh2').Client; var debugWebSSH2 = require('debug')('WebSSH2')
const CIDRMatcher = require('cidr-matcher'); var SSH = require('ssh2').Client
const validator = require('validator'); // var fs = require('fs')
const dnsPromises = require('dns').promises; // var hostkeys = JSON.parse(fs.readFileSync('./hostkeyhashes.json', 'utf8'))
const util = require('util'); var termCols, termRows
const { webssh2debug, auditLog, logError } = require('./logging'); var menuData = '<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>' +
'<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>'
/**
* parse conn errors
* @param {object} socket Socket object
* @param {object} err Error object
*/
function connError(socket, err) {
let msg = util.inspect(err);
const { session } = socket.request;
if (err?.level === 'client-authentication') {
msg = `Authentication failure user=${session.username} from=${socket.handshake.address}`;
socket.emit('allowreauth', session.ssh.allowreauth);
socket.emit('reauth');
}
if (err?.code === 'ENOTFOUND') {
msg = `Host not found: ${err.hostname}`;
}
if (err?.level === 'client-timeout') {
msg = `Connection Timeout: ${session.ssh.host}`;
}
logError(socket, 'CONN ERROR', msg);
}
/**
* check ssh host is in allowed subnet
* @param {object} socket Socket information
*/
async function checkSubnet(socket) {
let ipaddress = socket.request.session.ssh.host;
if (!validator.isIP(`${ipaddress}`)) {
try {
const result = await dnsPromises.lookup(socket.request.session.ssh.host);
ipaddress = result.address;
} catch (err) {
logError(
socket,
'CHECK SUBNET',
`${err.code}: ${err.hostname} user=${socket.request.session.username} from=${socket.handshake.address}`
);
socket.emit('ssherror', '404 HOST IP NOT FOUND');
socket.disconnect(true);
return;
}
}
const matcher = new CIDRMatcher(socket.request.session.ssh.allowedSubnets);
if (!matcher.contains(ipaddress)) {
logError(
socket,
'CHECK SUBNET',
`Requested host ${ipaddress} outside configured subnets / REJECTED user=${socket.request.session.username} from=${socket.handshake.address}`
);
socket.emit('ssherror', '401 UNAUTHORIZED');
socket.disconnect(true);
}
}
// public // public
module.exports = function appSocket(socket) { module.exports = function socket (socket) {
let login = false; // if websocket connection arrives without an express session, kill it
if (!socket.request.session) {
socket.once('disconnecting', (reason) => { socket.emit('401 UNAUTHORIZED')
webssh2debug(socket, `SOCKET DISCONNECTING: ${reason}`); debugWebSSH2('SOCKET: No Express Session / REJECTED')
if (login === true) { socket.disconnect(true)
auditLog( return
socket,
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
);
login = false;
}
});
async function setupConnection() {
// if websocket connection arrives without an express session, kill it
if (!socket.request.session) {
socket.emit('401 UNAUTHORIZED');
webssh2debug(socket, 'SOCKET: No Express Session / REJECTED');
socket.disconnect(true);
return;
}
// If configured, check that requsted host is in a permitted subnet
if (socket.request.session?.ssh?.allowedSubnets?.length > 0) {
checkSubnet(socket);
}
const conn = new SSH();
conn.on('banner', (data) => {
// need to convert to cr/lf for proper formatting
socket.emit('data', data.replace(/\r?\n/g, '\r\n').toString('utf-8'));
});
conn.on('handshake', () => {
socket.emit('setTerminalOpts', socket.request.session.ssh.terminal);
socket.emit('menu');
socket.emit('allowreauth', socket.request.session.ssh.allowreauth);
socket.emit('title', `ssh://${socket.request.session.ssh.host}`);
if (socket.request.session.ssh.header.background)
socket.emit('headerBackground', socket.request.session.ssh.header.background);
if (socket.request.session.ssh.header.name)
socket.emit('header', socket.request.session.ssh.header.name);
socket.emit(
'footer',
`ssh://${socket.request.session.username}@${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
);
});
conn.on('ready', () => {
webssh2debug(
socket,
`CONN READY: LOGIN: user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host} port=${socket.request.session.ssh.port} allowreplay=${socket.request.session.ssh.allowreplay} term=${socket.request.session.ssh.term}`
);
auditLog(
socket,
`LOGIN user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
);
login = true;
socket.emit('status', 'SSH CONNECTION ESTABLISHED');
socket.emit('statusBackground', 'green');
socket.emit('allowreplay', socket.request.session.ssh.allowreplay);
const { term, cols, rows } = socket.request.session.ssh;
conn.shell({ term, cols, rows }, (err, stream) => {
if (err) {
logError(socket, `EXEC ERROR`, err);
conn.end();
socket.disconnect(true);
return;
}
socket.once('disconnect', (reason) => {
webssh2debug(socket, `CLIENT SOCKET DISCONNECT: ${util.inspect(reason)}`);
conn.end();
socket.request.session.destroy();
});
socket.on('error', (errMsg) => {
webssh2debug(socket, `SOCKET ERROR: ${errMsg}`);
logError(socket, 'SOCKET ERROR', errMsg);
conn.end();
socket.disconnect(true);
});
socket.on('control', (controlData) => {
if (controlData === 'replayCredentials' && socket.request.session.ssh.allowreplay) {
stream.write(`${socket.request.session.userpassword}\n`);
}
if (controlData === 'reauth' && socket.request.session.username && login === true) {
auditLog(
socket,
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
);
login = false;
conn.end();
socket.disconnect(true);
}
webssh2debug(socket, `SOCKET CONTROL: ${controlData}`);
});
socket.on('resize', (data) => {
stream.setWindow(data.rows, data.cols);
webssh2debug(socket, `SOCKET RESIZE: ${JSON.stringify([data.rows, data.cols])}`);
});
socket.on('data', (data) => {
stream.write(data);
});
stream.on('data', (data) => {
socket.emit('data', data.toString('utf-8'));
});
stream.on('close', (code, signal) => {
webssh2debug(socket, `STREAM CLOSE: ${util.inspect([code, signal])}`);
if (socket.request.session?.username && login === true) {
auditLog(
socket,
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
);
login = false;
}
if (code !== 0 && typeof code !== 'undefined')
logError(socket, 'STREAM CLOSE', util.inspect({ message: [code, signal] }));
socket.disconnect(true);
conn.end();
});
stream.stderr.on('data', (data) => {
console.error(`STDERR: ${data}`);
});
});
});
conn.on('end', (err) => {
if (err) logError(socket, 'CONN END BY HOST', err);
webssh2debug(socket, 'CONN END BY HOST');
socket.disconnect(true);
});
conn.on('close', (err) => {
if (err) logError(socket, 'CONN CLOSE', err);
webssh2debug(socket, 'CONN CLOSE');
socket.disconnect(true);
});
conn.on('error', (err) => connError(socket, err));
conn.on('keyboard-interactive', (_name, _instructions, _instructionsLang, _prompts, finish) => {
webssh2debug(socket, 'CONN keyboard-interactive');
finish([socket.request.session.userpassword]);
});
if (
socket.request.session.username &&
(socket.request.session.userpassword || socket.request.session.privatekey) &&
socket.request.session.ssh
) {
// console.log('hostkeys: ' + hostkeys[0].[0])
const { ssh } = socket.request.session;
ssh.username = socket.request.session.username;
ssh.password = socket.request.session.userpassword;
ssh.tryKeyboard = true;
ssh.debug = debug('ssh2');
conn.connect(ssh);
} else {
webssh2debug(
socket,
`CONN CONNECT: Attempt to connect without session.username/password or session varialbles defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ${util.inspect(
socket.handshake
)}`
);
socket.emit('ssherror', 'WEBSOCKET ERROR - Refresh the browser and try again');
socket.request.session.destroy();
socket.disconnect(true);
}
} }
setupConnection(); var conn = new SSH()
}; socket.on('geometry', function socketOnGeometry (cols, rows) {
termCols = cols
termRows = rows
})
conn.on('banner', function connOnBanner (data) {
// need to convert to cr/lf for proper formatting
data = data.replace(/\r?\n/g, '\r\n')
socket.emit('data', data.toString('utf-8'))
})
conn.on('ready', function connOnReady () {
console.log('WebSSH2 Login: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' mrhsession=' + socket.request.session.ssh.mrhsession + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
socket.emit('menu', menuData)
socket.emit('allowreauth', socket.request.session.ssh.allowreauth)
socket.emit('setTerminalOpts', socket.request.session.ssh.terminal)
socket.emit('title', 'ssh://' + socket.request.session.ssh.host)
if (socket.request.session.ssh.header.background) socket.emit('headerBackground', socket.request.session.ssh.header.background)
if (socket.request.session.ssh.header.name) socket.emit('header', socket.request.session.ssh.header.name)
socket.emit('footer', 'ssh://' + socket.request.session.username + '@' + socket.request.session.ssh.host + ':' + socket.request.session.ssh.port)
socket.emit('status', 'SSH CONNECTION ESTABLISHED')
socket.emit('statusBackground', 'green')
socket.emit('allowreplay', socket.request.session.ssh.allowreplay)
conn.shell({
term: socket.request.session.ssh.term,
cols: termCols,
rows: termRows
}, function connShell (err, stream) {
if (err) {
SSHerror('EXEC ERROR' + err)
conn.end()
return
}
// poc to log commands from client
if (socket.request.session.ssh.serverlog.client) var dataBuffer
socket.on('data', function socketOnData (data) {
stream.write(data)
// poc to log commands from client
if (socket.request.session.ssh.serverlog.client) {
if (data === '\r') {
console.log('serverlog.client: ' + socket.request.session.id + '/' + socket.id + ' host: ' + socket.request.session.ssh.host + ' command: ' + dataBuffer)
dataBuffer = undefined
} else {
dataBuffer = (dataBuffer) ? dataBuffer + data : data
}
}
})
socket.on('control', function socketOnControl (controlData) {
switch (controlData) {
case 'replayCredentials':
if (socket.request.session.ssh.allowreplay) {
stream.write(socket.request.session.userpassword + '\n')
}
/* falls through */
default:
console.log('controlData: ' + controlData)
}
})
socket.on('resize', function socketOnResize (data) {
stream.setWindow(data.rows, data.cols)
})
socket.on('disconnecting', function socketOnDisconnecting (reason) { debugWebSSH2('SOCKET DISCONNECTING: ' + reason) })
socket.on('disconnect', function socketOnDisconnect (reason) {
debugWebSSH2('SOCKET DISCONNECT: ' + reason)
err = { message: reason }
SSHerror('CLIENT SOCKET DISCONNECT', err)
conn.end()
// socket.request.session.destroy()
})
socket.on('error', function socketOnError (err) {
SSHerror('SOCKET ERROR', err)
conn.end()
})
stream.on('data', function streamOnData (data) { socket.emit('data', data.toString('utf-8')) })
stream.on('close', function streamOnClose (code, signal) {
err = { message: ((code || signal) ? (((code) ? 'CODE: ' + code : '') + ((code && signal) ? ' ' : '') + ((signal) ? 'SIGNAL: ' + signal : '')) : undefined) }
SSHerror('STREAM CLOSE', err)
conn.end()
})
stream.stderr.on('data', function streamStderrOnData (data) {
console.log('STDERR: ' + data)
})
})
})
conn.on('end', function connOnEnd (err) { SSHerror('CONN END BY HOST', err) })
conn.on('close', function connOnClose (err) { SSHerror('CONN CLOSE', err) })
conn.on('error', function connOnError (err) { SSHerror('CONN ERROR', err) })
conn.on('keyboard-interactive', function connOnKeyboardInteractive (name, instructions, instructionsLang, prompts, finish) {
debugWebSSH2('conn.on(\'keyboard-interactive\')')
finish([socket.request.session.userpassword])
})
if (socket.request.session.username && socket.request.session.userpassword && socket.request.session.ssh) {
// console.log('hostkeys: ' + hostkeys[0].[0])
conn.connect({
host: socket.request.session.ssh.host,
port: socket.request.session.ssh.port,
username: socket.request.session.username,
password: socket.request.session.userpassword,
tryKeyboard: true,
algorithms: socket.request.session.ssh.algorithms,
readyTimeout: socket.request.session.ssh.readyTimeout,
keepaliveInterval: socket.request.session.ssh.keepaliveInterval,
keepaliveCountMax: socket.request.session.ssh.keepaliveCountMax,
debug: debug('ssh2')
})
} else {
debugWebSSH2('Attempt to connect without session.username/password or session varialbles defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ' + JSON.stringify(socket.handshake))
socket.emit('ssherror', 'WEBSOCKET ERROR - Refresh the browser and try again')
socket.request.session.destroy()
socket.disconnect(true)
}
/**
* Error handling for various events. Outputs error to client, logs to
* server, destroys session and disconnects socket.
* @param {string} myFunc Function calling this function
* @param {object} err error object or error message
*/
function SSHerror (myFunc, err) {
var theError
if (socket.request.session) {
// we just want the first error of the session to pass to the client
socket.request.session.error = (socket.request.session.error) || ((err) ? err.message : undefined)
theError = (socket.request.session.error) ? ': ' + socket.request.session.error : ''
// log unsuccessful login attempt
if (err && (err.level === 'client-authentication')) {
console.log('WebSSH2 ' + 'error: Authentication failure'.red.bold +
' user=' + socket.request.session.username.yellow.bold.underline +
' from=' + socket.handshake.address.yellow.bold.underline)
socket.emit('allowreauth', socket.request.session.ssh.allowreauth)
socket.emit('reauth')
} else {
console.log('WebSSH2 Logout: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
if (err) {
theError = (err) ? ': ' + err.message : ''
console.log('WebSSH2 error' + theError)
}
}
socket.emit('ssherror', 'SSH ' + myFunc + theError)
socket.request.session.destroy()
socket.disconnect(true)
} else {
theError = (err) ? ': ' + err.message : ''
socket.disconnect(true)
}
debugWebSSH2('SSHerror ' + myFunc + theError)
}
}

View file

@ -1,47 +1,30 @@
'use strict'
/* jshint esversion: 6, asi: true, node: true */ /* jshint esversion: 6, asi: true, node: true */
// util.js // util.js
// private // private
const debug = require('debug')('WebSSH2'); require('colors') // allow for color property extensions in log messages
const Auth = require('basic-auth'); var debug = require('debug')('WebSSH2')
var Auth = require('basic-auth')
let defaultCredentials = { username: null, password: null, privatekey: null }; exports.basicAuth = function basicAuth (req, res, next) {
var myAuth = Auth(req)
exports.setDefaultCredentials = function setDefaultCredentials({ if (myAuth && myAuth.pass !== '') {
name: username, req.session.username = myAuth.name
password, req.session.userpassword = myAuth.pass
privatekey, debug('myAuth.name: ' + myAuth.name.yellow.bold.underline +
overridebasic, ' and password ' + ((myAuth.pass) ? 'exists'.yellow.bold.underline
}) { : 'is blank'.underline.red.bold))
defaultCredentials = { username, password, privatekey, overridebasic }; next()
};
exports.basicAuth = function basicAuth(req, res, next) {
const myAuth = Auth(req);
// If Authorize: Basic header exists and the password isn't blank
// AND config.user.overridebasic is false, extract basic credentials
// from client]
const { username, password, privatekey, overridebasic } = defaultCredentials;
if (myAuth && myAuth.pass !== '' && !overridebasic) {
req.session.username = myAuth.name;
req.session.userpassword = myAuth.pass;
debug(`myAuth.name: ${myAuth.name} and password ${myAuth.pass ? 'exists' : 'is blank'}`);
} else { } else {
req.session.username = username; res.statusCode = 401
req.session.userpassword = password; debug('basicAuth credential request (401)')
req.session.privatekey = privatekey; res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"')
res.end('Username and password required for web SSH service.')
} }
if (!req.session.userpassword && !req.session.privatekey) { }
res.statusCode = 401;
debug('basicAuth credential request (401)');
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"');
res.end('Username and password required for web SSH service.');
return;
}
next();
};
// takes a string, makes it boolean (true if the string is true, false otherwise) // takes a string, makes it boolean (true if the string is true, false otherwise)
exports.parseBool = function parseBool(str) { exports.parseBool = function parseBool (str) {
return str.toLowerCase() === 'true'; return (str.toLowerCase() === 'true')
}; }

Binary file not shown.

View file

@ -0,0 +1 @@
b512ae8f04eba0eab29e026542fab1063b1bb4ae6db04e3613a8939260fe031c Build/Release/BIG-IP-ILX-WebSSH2-0.2.9.tgz

BIN
bun.lockb

Binary file not shown.

View file

@ -1,6 +0,0 @@
{
"dependencies": {},
"devDependencies": {
"bun-types": "^1.0.1"
}
}

View file

@ -1,14 +0,0 @@
{
"changelogPath": "CHANGELOG.md",
"include-v-in-tags": false,
"prerelease": true,
"packages": {
"app": {
"releaseType": "node",
"draft": false,
"bumpMinorPreMajor": false,
"bumpPatchForMinorPreMajor": false,
"versioning": "default"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
screenshots/demo-800.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

42
scripts/build.sh Executable file
View file

@ -0,0 +1,42 @@
#!/bin/bash
## Syncs from BIG-IP and builds a release based on version in extensions/ephemeral_auth/package.json
#
source ./scripts/env.sh
source ./scripts/util.sh
./scripts/pull.sh
if [ $? -ne 0 ]; then
# failure
tput bel;tput bel;tput bel;tput bel
echo -e "\n${fgLtRed}Pull command failed. Giving up.${fgLtWhi}\n"
echo ${output}
exit 255
fi
# get version of package from package.json
package_version=$(jq -r ".version" workspace/extensions/webssh2/package.json)
# creates new workspace name with version
webssh_workspace_name=$webssh_workspace_name-$package_version
echoNotice "Creating workspace package"
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost /bin/tar --exclude='./extensions/webssh2/config.json' -czf - -C /var/ilx/workspaces/Common/$webssh_workspace_name . > Build/Release/$webssh_package_name-$package_version.tgz"
echoNotice "Creating SHA256 hash"
runCommand "shasum -a 256 Build/Release/$webssh_package_name-$package_version.tgz > Build/Release/$webssh_package_name-$package_version.tgz.sha256"
echoNotice "Copying to current"
runCommand "cp Build/Release/$webssh_package_name-$package_version.tgz $webssh_pua_location/$webssh_package_name-current.tgz && \
cp Build/Release/$webssh_package_name-$package_version.tgz.sha256 $webssh_pua_location/$webssh_package_name-current.tgz.sha256"
echoNotice "Deleting any '.DS_Store' files"
runCommand "find . -name '.DS_Store' -type f -delete"
echo -e "\nWorkspace packages located at:\n"
echo " Build/Release/$webssh_package_name-$package_version.tgz"
echo " Build/Release/$webssh_package_name-$package_version.tgz.sha256"
echo " $webssh_pua_location/$webssh_package_name-current.tgz"
echo " $webssh_pua_location/$webssh_package_name-current.tgz.sha256"
echo -e "\n👍 Build Complete 👍\n"
exit 0

6
scripts/env.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/sh
#webssh_ilxhost=root@192.168.30.209
webssh_ilxhost=root@192.168.30.203
webssh_workspace_name=webssh2
webssh_package_name=BIG-IP-ILX-WebSSH2
webssh_pua_location=./bin

30
scripts/pull.sh Executable file
View file

@ -0,0 +1,30 @@
#!/bin/bash
#
# ./scripts/pull.sh
#
# bill@f5.com
#
# Pulls an ILX workspace from a BIG-IP and syncs to ./workspace, excludes
# ./workspace/extensions/ephemeral_auth/node_modules.
#
source ./scripts/env.sh
source ./scripts/util.sh
# get version of package from package.json
PACKAGE_VERSION=$(jq -r ".version" workspace/extensions/webssh2/package.json 2>&1)
# creates new workspace name with version
webssh_workspace_name=$webssh_workspace_name-$PACKAGE_VERSION
echo "Pull ${fgLtCya}$webssh_workspace_name${fgLtWhi} from ${fgLtCya}$webssh_ilxhost${fgLtWhi}"
# check to see if the workspace actually exists before attempting to copy over
echoNotice "Checking for existing workspace ${fgLtCya}$webssh_workspace_name${fgLtWhi}"
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh list ilx workspace $webssh_workspace_name one-line 2>&1"
echoNotice "Pulling ${fgLtCya}$webssh_workspace_name${fgLtWhi} from ${fgLtCya}$webssh_ilxhost${fgLtWhi}"
runCommand "rsync -e 'ssh -o ClearAllForwardings=yes -ax' -avq --include=\"extensions/ephemeral_auth/node_modules/f5-*\" --exclude=\".DS_Store\" --exclude=\"extensions/ephemeral_auth/node_modules/*\" $webssh_ilxhost:/var/ilx/workspaces/Common/$webssh_workspace_name/. workspace/. 2>&1"
echo -e "\n👍 Pull complete 👍\n"
exit 0

61
scripts/push.sh Executable file
View file

@ -0,0 +1,61 @@
#!/bin/bash
#
# ./scripts/push.sh
#
# bill@f5.com
#
# Pushes ./workspace to a BIG-IP ILX workspace
#
source ./scripts/env.sh
source ./scripts/util.sh
# get version of package from package.json
PACKAGE_VERSION=$(jq -r ".version" workspace/extensions/webssh2/package.json 2>&1)
# creates new workspace name with version
webssh_workspace_name=$webssh_workspace_name-$PACKAGE_VERSION
echo "Push ${fgLtCya}$webssh_workspace_name${fgLtWhi} to ${fgLtCya}$webssh_ilxhost${fgLtWhi}"
echoNotice "Checking $webssh_ilxhost for workspace $webssh_workspace_name"
output=$(ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh list ilx workspace $webssh_workspace_name one-line 2>&1)
result="$?" 2>&1
if [ $result -ne 0 ]; then
echo "❌"
echoNotice "Attempting to create workspace"
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost \"tmsh create ilx workspace $webssh_workspace_name node-version 6.9.1\" 2>&1"
else
echo "✅"
fi
echoNotice "Pushing ./workspace to $webssh_ilxhost at $webssh_workspace_name"
runCommand "rsync -e 'ssh -o ClearAllForwardings=yes -ax' -avq --delete --exclude='.DS_Store' --exclude extensions/webssh2/node_modules workspace/. $webssh_ilxhost:/var/ilx/workspaces/Common/$webssh_workspace_name/."
echoNotice "Installing node modules at $webssh_workspace_name on $webssh_ilxhost"
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost \"cd /var/ilx/workspaces/Common/$webssh_workspace_name/extensions/webssh2; npm i --production\" 2>&1"
echoNotice "Setting permissions at $webssh_workspace_name on $webssh_ilxhost"
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost \"chown -R root.sdm /var/ilx/workspaces/Common/$webssh_workspace_name/; \
chmod -R ug+rwX,o-w /var/ilx/workspaces/Common/$webssh_workspace_name/; \
chmod u+rw,go-w /var/ilx/workspaces/Common/$webssh_workspace_name/version; \
chmod u+rw,go-w /var/ilx/workspaces/Common/$webssh_workspace_name/node_version\" 2>&1"
echoNotice "Deleting $webssh_workspace_name/node_modules/.bin on $webssh_ilxhost"
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost \"cd /var/ilx/workspaces/Common/$webssh_workspace_name/extensions/webssh2; rm -rf node_modules/.bin\" 2>&1"
# switch plugin to new workspace
echoNotice "Checking to see if plugin exists"
output=$(ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh list ilx plugin WebSSH_plugin one-line 2>&1)
result="$?" 2>&1
if [ $result -ne 0 ]; then
echo "❌"
echoNotice "Attempting to create plugin"
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh create ilx plugin WebSSH_plugin from-workspace $webssh_workspace_name extensions { webssh2 { concurrency-mode single ilx-logging enabled } } 2>&1"
else
echo "✅"
echoNotice "Switching plugin to $webssh_workspace_name"
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh modify ilx plugin WebSSH_plugin from-workspace $webssh_workspace_name extensions { webssh2 { concurrency-mode single ilx-logging enabled } } 2>&1"
fi
echo -e "\n👍 Push complete 👍\n"
exit 0

74
scripts/util.sh Executable file
View file

@ -0,0 +1,74 @@
#!/bin/bash
# Utility functions / scripts
echoNotice () { echo -e -n "\n$@... "; }
fgLtRed=$(tput bold;tput setaf 1)
fgLtGrn=$(tput bold;tput setaf 2)
fgLtYel=$(tput bold;tput setaf 3)
fgLtBlu=$(tput bold;tput setaf 4)
fgLtMag=$(tput bold;tput setaf 5)
fgLtCya=$(tput bold;tput setaf 6)
fgLtWhi=$(tput bold;tput setaf 7)
fgLtGry=$(tput bold;tput setaf 8)
echo ${fgLtWhi}
# check for jq and try to install...
output=$(which jq 2>&1)
if [[ $? -ne 0 ]]; then
echo -e "You need to install jq: https://stedolan.github.io/jq\n"
echo -e "If you have *brew* you can install with:\n"
echo -e " brew install jq\n"
echo -n "Do you want me to try and install that for you (Y/n)? "
read -n1 yesno
echo
if [[ ("$yesno" != "y") ]]; then
echo -e "\nUnable to continue, install jq first.\n\n"
exit 255
else
which brew
if [[ $? -ne 0 ]]; then
echo -e "\nYou're a mess... You don't even have brew installed...\nMaybe you should check it out\n"
echo -e " https://brew.sh/\n\n"
exit 255
fi
echo
brew install jq
if [[ $? -ne 0 ]]; then
echo -e "\nLooks like that failed, I can't do everything... Quitting, install jq...\n"
exit 255
fi
fi
fi
# checks the output of a command to get the status and report/handle failure
checkOutput() {
if [ $result -eq 0 ]; then
# success
#echo "${fgLtGrn}[OK]${fgLtWhi}"
echo "✅"
return
else
# failure
tput bel;tput bel;tput bel;tput bel
#echo "${fgLtRed}[FAILED]${fgLtWhi}"
echo "❌"
echo -e "\nPrevious command failed in ${script_path}/${scriptname} with error level: ${result}"
echo -e "\nCommand:\n"
echo " ${command}"
echo -e "\nSTDOUT/STDERR:\n"
echo ${output}
exit 255
fi
}
# run a comand and check call checkOutput
runCommand() {
# $1 command
command=$@
output=$((eval $command) 2>&1)
result="$?" 2>&1
prevline=$(($LINENO-2))
checkOutput
}

29
scripts/ver.sh Executable file
View file

@ -0,0 +1,29 @@
#!/bin/bash
## displays and optionally changes version of product
source ./scripts/env.sh
source ./scripts/util.sh
echo
# get current version of workspace, ask to change or rebuild
webssh_ilx_ver=$(jq -r ".version" ./workspace/extensions/webssh2/package.json 2>&1)
if [[ $? -ne 0 ]]; then exit; echo "error reading ILX irule version";fi
echo "Current version of $webssh_workspace_name is: $webssh_ilx_ver"
echo -n "If you want to change this version, enter it now otherwise press enter to retain: "
read newver
echo
if [[ ("$newver" != "") ]]; then
echo "Updating version of ILX to: $newver"
export newver
jq --arg newver "$newver" '.version = $newver' < ./workspace/extensions/webssh2/package.json > ./workspace/extensions/webssh2/package.json.new
if [[ $? -ne 0 ]]; then exit; echo "error changing version - ilx";fi
mv ./workspace/extensions/webssh2/package.json.new ./workspace/extensions/webssh2/package.json
else
echo "No changes made"
fi