Compare commits
No commits in common. "dev" and "v2.0.2" have entirely different histories.
113 changed files with 11467 additions and 7208 deletions
|
@ -13,7 +13,9 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
// run arguments passed to docker
|
// run arguments passed to docker
|
||||||
"runArgs": ["--security-opt", "label=disable"],
|
"runArgs": [
|
||||||
|
"--security-opt", "label=disable"
|
||||||
|
],
|
||||||
|
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
// extensions to preload before other extensions
|
// extensions to preload before other extensions
|
||||||
|
@ -29,11 +31,15 @@
|
||||||
"onCreateCommand": "nix-shell --command 'echo done building nix dev environment'",
|
"onCreateCommand": "nix-shell --command 'echo done building nix dev environment'",
|
||||||
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
"forwardPorts": [8000],
|
"forwardPorts": [
|
||||||
|
3000
|
||||||
|
],
|
||||||
|
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": ["arrterian.nix-env-selector"]
|
"extensions": [
|
||||||
|
"arrterian.nix-env-selector"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,23 @@ jobs:
|
||||||
username: imterah
|
username: imterah
|
||||||
password: ${{secrets.ACTIONS_PACKAGES_DEPL_KEY}}
|
password: ${{secrets.ACTIONS_PACKAGES_DEPL_KEY}}
|
||||||
|
|
||||||
- name: Build Docker image
|
- name: Build Docker images
|
||||||
run: |
|
run: |
|
||||||
docker build . --tag ghcr.io/imterah/hermes:$GITHUB_REF_NAME
|
docker build ./backend --tag ghcr.io/imterah/hermes:$GITHUB_REF_NAME
|
||||||
|
docker build -t ghcr.io/imterah/hermes-backend-migration:$GITHUB_REF_NAME -f MigrationDockerfile .
|
||||||
|
|
||||||
- name: Upload Docker image
|
docker build ./sshfrontend --tag ghcr.io/imterah/hermes-lom:$GITHUB_REF_NAME
|
||||||
|
|
||||||
|
- name: Upload all Docker images
|
||||||
run: |
|
run: |
|
||||||
docker tag ghcr.io/imterah/hermes:$GITHUB_REF_NAME ghcr.io/imterah/hermes:latest
|
docker tag ghcr.io/imterah/hermes:$GITHUB_REF_NAME ghcr.io/imterah/hermes:latest
|
||||||
docker push ghcr.io/imterah/hermes:$GITHUB_REF_NAME
|
docker push ghcr.io/imterah/hermes:$GITHUB_REF_NAME
|
||||||
docker push ghcr.io/imterah/hermes:latest
|
docker push ghcr.io/imterah/hermes:latest
|
||||||
|
|
||||||
|
docker tag ghcr.io/imterah/hermes-backend-migration:$GITHUB_REF_NAME ghcr.io/imterah/hermes-backend-migration:latest
|
||||||
|
docker push ghcr.io/imterah/hermes-backend-migration:$GITHUB_REF_NAME
|
||||||
|
docker push ghcr.io/imterah/hermes-backend-migration:latest
|
||||||
|
|
||||||
|
docker tag ghcr.io/imterah/hermes-lom:$GITHUB_REF_NAME ghcr.io/imterah/hermes-lom:latest
|
||||||
|
docker push ghcr.io/imterah/hermes-lom:$GITHUB_REF_NAME
|
||||||
|
docker push ghcr.io/imterah/hermes-lom:latest
|
||||||
|
|
2
.gitconfig
Normal file
2
.gitconfig
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[core]
|
||||||
|
hooksPath = .githooks/
|
18
.githooks/pre-commit
Executable file
18
.githooks/pre-commit
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
shopt -s globstar
|
||||||
|
set -e
|
||||||
|
|
||||||
|
ROOT="$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
pushd $ROOT/api
|
||||||
|
npx eslint src
|
||||||
|
popd
|
||||||
|
|
||||||
|
pushd $ROOT/lom
|
||||||
|
npx eslint src
|
||||||
|
popd
|
||||||
|
|
||||||
|
# Formatting step
|
||||||
|
$ROOT/api/node_modules/.bin/prettier --ignore-unknown --write $ROOT/{api,lom}/{eslint.config.js,src/**/*.ts}
|
||||||
|
git update-index --again
|
||||||
|
exit 0
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,15 +1,15 @@
|
||||||
# Go artifacts
|
# Go artifacts
|
||||||
backend/api/api
|
|
||||||
backend/sshbackend/sshbackend
|
backend/sshbackend/sshbackend
|
||||||
backend/dummybackend/dummybackend
|
backend/dummybackend/dummybackend
|
||||||
backend/sshappbackend/local-code/remote-bin
|
|
||||||
backend/sshappbackend/local-code/sshappbackend
|
|
||||||
backend/externalbackendlauncher/externalbackendlauncher
|
backend/externalbackendlauncher/externalbackendlauncher
|
||||||
frontend/frontend
|
backend/api/api
|
||||||
|
|
||||||
# Backup artifacts
|
# Backup artifacts
|
||||||
*.json.gz
|
*.json.gz
|
||||||
|
|
||||||
|
# LOM
|
||||||
|
sshfrontend/keys
|
||||||
|
|
||||||
# Output
|
# Output
|
||||||
out
|
out
|
||||||
|
|
||||||
|
|
16
.prettierrc
Normal file
16
.prettierrc
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"htmlWhitespaceSensitivity": "css",
|
||||||
|
"insertPragma": false,
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"printWidth": 80,
|
||||||
|
"proseWrap": "always",
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"requirePragma": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"useTabs": false
|
||||||
|
}
|
49
CHANGELOG.md
Normal file
49
CHANGELOG.md
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [v1.1.2](https://github.com/imterah/nextnet/tree/v1.1.2) (2024-09-29)
|
||||||
|
|
||||||
|
## [v1.1.1](https://github.com/imterah/nextnet/tree/v1.1.1) (2024-09-29)
|
||||||
|
|
||||||
|
## [v1.1.0](https://github.com/imterah/nextnet/tree/v1.1.0) (2024-09-22)
|
||||||
|
|
||||||
|
**Fixed bugs:**
|
||||||
|
|
||||||
|
- Desktop app fails to build on macOS w/ `nix-shell` [\#1](https://github.com/imterah/nextnet/issues/1)
|
||||||
|
|
||||||
|
**Merged pull requests:**
|
||||||
|
|
||||||
|
- chore\(deps\): bump find-my-way from 8.1.0 to 8.2.2 in /api [\#17](https://github.com/imterah/nextnet/pull/17)
|
||||||
|
- chore\(deps\): bump axios from 1.6.8 to 1.7.4 in /lom [\#16](https://github.com/imterah/nextnet/pull/16)
|
||||||
|
- chore\(deps\): bump micromatch from 4.0.5 to 4.0.8 in /lom [\#15](https://github.com/imterah/nextnet/pull/15)
|
||||||
|
- chore\(deps\): bump braces from 3.0.2 to 3.0.3 in /lom [\#13](https://github.com/imterah/nextnet/pull/13)
|
||||||
|
- chore\(deps-dev\): bump braces from 3.0.2 to 3.0.3 in /api [\#11](https://github.com/imterah/nextnet/pull/11)
|
||||||
|
- chore\(deps\): bump ws from 8.17.0 to 8.17.1 in /api [\#10](https://github.com/imterah/nextnet/pull/10)
|
||||||
|
|
||||||
|
## [v1.0.1](https://github.com/imterah/nextnet/tree/v1.0.1) (2024-05-18)
|
||||||
|
|
||||||
|
**Merged pull requests:**
|
||||||
|
|
||||||
|
- Adds public key authentication [\#6](https://github.com/imterah/nextnet/pull/6)
|
||||||
|
- Add support for eslint [\#5](https://github.com/imterah/nextnet/pull/5)
|
||||||
|
|
||||||
|
## [v1.0.0](https://github.com/imterah/nextnet/tree/v1.0.0) (2024-05-10)
|
||||||
|
|
||||||
|
## [v0.1.1](https://github.com/imterah/nextnet/tree/v0.1.1) (2024-05-05)
|
||||||
|
|
||||||
|
## [v0.1.0](https://github.com/imterah/nextnet/tree/v0.1.0) (2024-05-05)
|
||||||
|
|
||||||
|
**Implemented enhancements:**
|
||||||
|
|
||||||
|
- \(potentially\) Migrate nix shell to nix flake [\#2](https://github.com/imterah/nextnet/issues/2)
|
||||||
|
|
||||||
|
**Closed issues:**
|
||||||
|
|
||||||
|
- add precommit hooks [\#3](https://github.com/imterah/nextnet/issues/3)
|
||||||
|
|
||||||
|
**Merged pull requests:**
|
||||||
|
|
||||||
|
- Reimplements PassyFire as a possible backend [\#4](https://github.com/imterah/nextnet/pull/4)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
11
Dockerfile
11
Dockerfile
|
@ -1,11 +0,0 @@
|
||||||
FROM golang:latest AS build
|
|
||||||
WORKDIR /build
|
|
||||||
COPY . /build
|
|
||||||
RUN cd backend; bash build.sh
|
|
||||||
FROM busybox:stable-glibc AS run
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=build /build/backend/backends.prod.json /app/backends.json
|
|
||||||
COPY --from=build /build/backend/api/api /app/hermes
|
|
||||||
COPY --from=build /build/backend/sshbackend/sshbackend /app/sshbackend
|
|
||||||
COPY --from=build /build/backend/sshappbackend/local-code/sshappbackend /app/sshappbackend
|
|
||||||
ENTRYPOINT ["/app/hermes", "--backends-path", "/app/backends.json"]
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
BSD 3-Clause License
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) 2024, Tera
|
Copyright (c) 2024, Greyson
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are met:
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
25
MigrationDockerfile
Normal file
25
MigrationDockerfile
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
FROM golang:latest AS build
|
||||||
|
WORKDIR /build
|
||||||
|
COPY backend /build
|
||||||
|
RUN cd api; go build .
|
||||||
|
FROM node:22.11.0-bookworm AS run
|
||||||
|
LABEL org.opencontainers.image.source="https://git.terah.dev/imterah/nextnet"
|
||||||
|
COPY migration-entrypoint.sh /app/entrypoint.sh
|
||||||
|
COPY backend-legacy/src /app/legacy/src
|
||||||
|
COPY backend-legacy/prisma /app/legacy/prisma
|
||||||
|
COPY backend-legacy/tsconfig.json /app/legacy/
|
||||||
|
COPY backend-legacy/package.json /app/legacy/
|
||||||
|
COPY backend-legacy/package-lock.json /app/legacy/
|
||||||
|
WORKDIR /app/legacy
|
||||||
|
RUN apt update
|
||||||
|
RUN apt install postgresql -y
|
||||||
|
RUN npm install --save-dev
|
||||||
|
RUN npm run build
|
||||||
|
RUN rm out/**/*.ts out/**/*.map
|
||||||
|
RUN rm -rf src
|
||||||
|
RUN npm prune --production
|
||||||
|
WORKDIR /app/modern
|
||||||
|
COPY --from=build /build/api/api /app/modern/hermes
|
||||||
|
RUN echo "{}" >> /app/modern/backends.json
|
||||||
|
WORKDIR /app
|
||||||
|
ENTRYPOINT sh entrypoint.sh
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
1. Copy and change the default password (or username & db name too) from the template file `prod-docker.env`:
|
1. Copy and change the default password (or username & db name too) from the template file `prod-docker.env`:
|
||||||
```bash
|
```bash
|
||||||
sed -e "s/POSTGRES_PASSWORD=hermes/POSTGRES_PASSWORD=$(head -c 500 /dev/random | sha512sum | cut -d " " -f 1)/g" -e "s/JWT_SECRET=hermes/JWT_SECRET=$(head -c 500 /dev/random | sha512sum | cut -d " " -f 1)/g" prod-docker.env > .env
|
sed "s/POSTGRES_PASSWORD=hermes/POSTGRES_PASSWORD=$(head -c 500 /dev/random | sha512sum | cut -d " " -f 1)/g" prod-docker.env > .env
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Build the docker stack: `docker compose --env-file .env up -d`
|
2. Build the docker stack: `docker compose --env-file .env up -d`
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
package apiclient
|
|
||||||
|
|
||||||
import "git.terah.dev/imterah/hermes/apiclient/users"
|
|
||||||
|
|
||||||
type HermesAPIClient struct {
|
|
||||||
URL string
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Users
|
|
||||||
|
|
||||||
func (api *HermesAPIClient) UserGetRefreshToken(username *string, email *string, password string) (string, error) {
|
|
||||||
return users.GetRefreshToken(api.URL, username, email, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *HermesAPIClient) UserGetJWTFromToken(refreshToken string) (string, error) {
|
|
||||||
return users.GetJWTFromToken(api.URL, refreshToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *HermesAPIClient) UserCreate(fullName, username, email, password string, isBot bool) (string, error) {
|
|
||||||
return users.CreateUser(api.URL, fullName, username, email, password, isBot)
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
package backendstructs
|
|
||||||
|
|
||||||
type BackendCreationRequest struct {
|
|
||||||
Token string `validate:"required"`
|
|
||||||
Name string `validate:"required"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
Backend string `validate:"required"`
|
|
||||||
BackendParameters interface{} `json:"connectionDetails" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BackendLookupRequest struct {
|
|
||||||
Token string `validate:"required"`
|
|
||||||
BackendID *uint `json:"id"`
|
|
||||||
Name *string `json:"name"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
Backend *string `json:"backend"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BackendRemovalRequest struct {
|
|
||||||
Token string `validate:"required"`
|
|
||||||
BackendID uint `json:"id" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionsRequest struct {
|
|
||||||
Token string `validate:"required" json:"token"`
|
|
||||||
Id uint `validate:"required" json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyCreationRequest struct {
|
|
||||||
Token string `validate:"required" json:"token"`
|
|
||||||
Name string `validate:"required" json:"name"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
Protocol string `validate:"required" json:"protocol"`
|
|
||||||
SourceIP string `validate:"required" json:"sourceIP"`
|
|
||||||
SourcePort uint16 `validate:"required" json:"sourcePort"`
|
|
||||||
DestinationPort uint16 `validate:"required" json:"destinationPort"`
|
|
||||||
ProviderID uint `validate:"required" json:"providerID"`
|
|
||||||
AutoStart *bool `json:"autoStart"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyLookupRequest struct {
|
|
||||||
Token string `validate:"required" json:"token"`
|
|
||||||
Id *uint `json:"id"`
|
|
||||||
Name *string `json:"name"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
Protocol *string `json:"protocol"`
|
|
||||||
SourceIP *string `json:"sourceIP"`
|
|
||||||
SourcePort *uint16 `json:"sourcePort"`
|
|
||||||
DestinationPort *uint16 `json:"destPort"`
|
|
||||||
ProviderID *uint `json:"providerID"`
|
|
||||||
AutoStart *bool `json:"autoStart"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyRemovalRequest struct {
|
|
||||||
Token string `validate:"required" json:"token"`
|
|
||||||
ID uint `validate:"required" json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyStartRequest struct {
|
|
||||||
Token string `validate:"required" json:"token"`
|
|
||||||
ID uint `validate:"required" json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyStopRequest struct {
|
|
||||||
Token string `validate:"required" json:"token"`
|
|
||||||
ID uint `validate:"required" json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserCreationRequest struct {
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Email string `json:"email" validate:"required"`
|
|
||||||
Password string `json:"password" validate:"required"`
|
|
||||||
Username string `json:"username" validate:"required"`
|
|
||||||
|
|
||||||
ExistingUserToken string `json:"token"`
|
|
||||||
IsBot bool `json:"isBot"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserLoginRequest struct {
|
|
||||||
Username *string `json:"username"`
|
|
||||||
Email *string `json:"email"`
|
|
||||||
|
|
||||||
Password string `json:"password" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserLookupRequest struct {
|
|
||||||
Token string `validate:"required"`
|
|
||||||
UID *uint `json:"id"`
|
|
||||||
Name *string `json:"name"`
|
|
||||||
Email *string `json:"email"`
|
|
||||||
Username *string `json:"username"`
|
|
||||||
IsBot *bool `json:"isServiceAccount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRefreshRequest struct {
|
|
||||||
Token string `json:"token" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRemovalRequest struct {
|
|
||||||
Token string `json:"token" validate:"required"`
|
|
||||||
UID *uint `json:"uid"`
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/apiclient/backendstructs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type refreshTokenResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
RefreshToken string `json:"refreshToken"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type jwtTokenResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
JWT string `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRefreshToken(url string, username, email *string, password string) (string, error) {
|
|
||||||
body, err := json.Marshal(&backendstructs.UserLoginRequest{
|
|
||||||
Username: username,
|
|
||||||
Email: email,
|
|
||||||
Password: password,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := http.Post(fmt.Sprintf("%s/api/v1/users/login", url), "application/json", bytes.NewBuffer(body))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyContents, err := io.ReadAll(res.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to read response body: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &refreshTokenResponse{}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(bodyContents, response); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !response.Success {
|
|
||||||
return "", fmt.Errorf("failed to get refresh token")
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.RefreshToken == "" {
|
|
||||||
return "", fmt.Errorf("refresh token is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.RefreshToken, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetJWTFromToken(url, refreshToken string) (string, error) {
|
|
||||||
body, err := json.Marshal(&backendstructs.UserRefreshRequest{
|
|
||||||
Token: refreshToken,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := http.Post(fmt.Sprintf("%s/api/v1/users/refresh", url), "application/json", bytes.NewBuffer(body))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyContents, err := io.ReadAll(res.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to read response body: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &jwtTokenResponse{}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(bodyContents, response); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !response.Success {
|
|
||||||
return "", fmt.Errorf("failed to get JWT token")
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.JWT == "" {
|
|
||||||
return "", fmt.Errorf("JWT token is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.JWT, nil
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/apiclient/backendstructs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type createUserResponse struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
Success bool `json:"success"`
|
|
||||||
RefreshToken string `json:"refreshToken"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateUser(url, fullName, username, email, password string, isBot bool) (string, error) {
|
|
||||||
body, err := json.Marshal(&backendstructs.UserCreationRequest{
|
|
||||||
Username: username,
|
|
||||||
Name: fullName,
|
|
||||||
Email: email,
|
|
||||||
Password: password,
|
|
||||||
IsBot: isBot,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := http.Post(fmt.Sprintf("%s/api/v1/users/create", url), "application/json", bytes.NewBuffer(body))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyContents, err := io.ReadAll(res.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to read response body: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &createUserResponse{}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(bodyContents, response); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.Error != "" {
|
|
||||||
return "", fmt.Errorf("error from server: %s", response.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !response.Success {
|
|
||||||
return "", fmt.Errorf("failed to get refresh token")
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.RefreshToken == "" {
|
|
||||||
return "", fmt.Errorf("refresh token is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.RefreshToken, nil
|
|
||||||
}
|
|
15
backend-legacy/Dockerfile
Normal file
15
backend-legacy/Dockerfile
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
FROM node:22.11.0-bookworm
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/greysoh/nextnet"
|
||||||
|
WORKDIR /app/
|
||||||
|
COPY src /app/src
|
||||||
|
COPY prisma /app/prisma
|
||||||
|
COPY docker-entrypoint.sh /app/
|
||||||
|
COPY tsconfig.json /app/
|
||||||
|
COPY package.json /app/
|
||||||
|
COPY package-lock.json /app/
|
||||||
|
RUN npm install --save-dev
|
||||||
|
RUN npm run build
|
||||||
|
RUN rm out/**/*.ts out/**/*.map
|
||||||
|
RUN rm -rf src
|
||||||
|
RUN npm prune --production
|
||||||
|
ENTRYPOINT sh docker-entrypoint.sh
|
7
backend-legacy/dev.env
Normal file
7
backend-legacy/dev.env
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Environment variables declared in this file are automatically made available to Prisma.
|
||||||
|
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
|
||||||
|
|
||||||
|
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
||||||
|
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||||
|
|
||||||
|
DATABASE_URL="postgresql://nextnet:nextnet@localhost:5432/nextnet?schema=nextnet"
|
12
backend-legacy/docker-entrypoint.sh
Normal file
12
backend-legacy/docker-entrypoint.sh
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
export NODE_ENV="production"
|
||||||
|
|
||||||
|
if [[ "$DATABASE_URL" == "" ]]; then
|
||||||
|
export DATABASE_URL="postgresql://$POSTGRES_USERNAME:$POSTGRES_PASSWORD@nextnet-postgres:5432/$POSTGRES_DB?schema=nextnet"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Welcome to NextNet."
|
||||||
|
echo "Running database migrations..."
|
||||||
|
npx prisma migrate deploy
|
||||||
|
echo "Starting application..."
|
||||||
|
npm start
|
19
backend-legacy/eslint.config.js
Normal file
19
backend-legacy/eslint.config.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import globals from "globals";
|
||||||
|
import pluginJs from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: globals.node,
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
3302
backend-legacy/package-lock.json
generated
Normal file
3302
backend-legacy/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
38
backend-legacy/package.json
Normal file
38
backend-legacy/package.json
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "nextnet",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"description": "Yet another dashboard to manage portforwarding technologies",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "cd out && node --enable-source-maps index.js",
|
||||||
|
"dev": "nodemon --watch src --ext ts,js,mjs,json --exec \"tsc && cd out && node --enable-source-maps index.js\""
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "greysoh",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.16.0",
|
||||||
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
"@types/node": "^22.10.1",
|
||||||
|
"@types/ssh2": "^1.15.1",
|
||||||
|
"@types/ws": "^8.5.13",
|
||||||
|
"eslint": "^9.16.0",
|
||||||
|
"globals": "^15.12.0",
|
||||||
|
"nodemon": "^3.1.7",
|
||||||
|
"pino-pretty": "^13.0.0",
|
||||||
|
"prettier": "^3.4.1",
|
||||||
|
"prisma": "^5.22.0",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"typescript-eslint": "^8.16.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/websocket": "^11.0.1",
|
||||||
|
"@prisma/client": "^6.0.0",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"fastify": "^5.1.0",
|
||||||
|
"node-ssh": "^13.2.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DesinationProvider" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT,
|
||||||
|
"backend" TEXT NOT NULL,
|
||||||
|
"connectionDetails" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "DesinationProvider_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ForwardRule" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT,
|
||||||
|
"sourceIP" TEXT NOT NULL,
|
||||||
|
"sourcePort" INTEGER NOT NULL,
|
||||||
|
"destIP" TEXT NOT NULL,
|
||||||
|
"destPort" INTEGER NOT NULL,
|
||||||
|
"destProviderID" INTEGER NOT NULL,
|
||||||
|
"enabled" BOOLEAN NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "ForwardRule_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Permission" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"permission" TEXT NOT NULL,
|
||||||
|
"has" BOOLEAN NOT NULL,
|
||||||
|
"userID" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Permission_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
"rootToken" TEXT,
|
||||||
|
"isRootServiceAccount" BOOLEAN,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Permission" ADD CONSTRAINT "Permission_userID_fkey" FOREIGN KEY ("userID") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `destIP` on the `ForwardRule` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ForwardRule" DROP COLUMN "destIP";
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `protocol` to the `ForwardRule` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ForwardRule" ADD COLUMN "protocol" TEXT NOT NULL;
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "username" TEXT;
|
3
backend-legacy/prisma/migrations/migration_lock.toml
Normal file
3
backend-legacy/prisma/migrations/migration_lock.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (i.e. Git)
|
||||||
|
provider = "postgresql"
|
54
backend-legacy/prisma/schema.prisma
Normal file
54
backend-legacy/prisma/schema.prisma
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model DesinationProvider {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
|
||||||
|
name String
|
||||||
|
description String?
|
||||||
|
backend String
|
||||||
|
connectionDetails String
|
||||||
|
}
|
||||||
|
|
||||||
|
model ForwardRule {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
|
||||||
|
name String
|
||||||
|
description String?
|
||||||
|
protocol String
|
||||||
|
sourceIP String
|
||||||
|
sourcePort Int
|
||||||
|
destPort Int
|
||||||
|
destProviderID Int
|
||||||
|
enabled Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
model Permission {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
|
||||||
|
permission String
|
||||||
|
has Boolean
|
||||||
|
user User @relation(fields: [userID], references: [id])
|
||||||
|
userID Int
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
|
||||||
|
email String @unique
|
||||||
|
username String? // NOT optional in the API, but just for backwards compat
|
||||||
|
name String
|
||||||
|
password String // Will be hashed using bcrypt
|
||||||
|
rootToken String?
|
||||||
|
isRootServiceAccount Boolean?
|
||||||
|
permissions Permission[]
|
||||||
|
}
|
52
backend-legacy/src/tools/exportDBContents.ts
Normal file
52
backend-legacy/src/tools/exportDBContents.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { createReadStream, createWriteStream } from "node:fs";
|
||||||
|
import { Readable, pipeline } from "node:stream";
|
||||||
|
import { createGzip } from "node:zlib";
|
||||||
|
import process from "node:process";
|
||||||
|
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
|
const gzip = createGzip();
|
||||||
|
|
||||||
|
if (process.argv.length <= 2) {
|
||||||
|
console.error(
|
||||||
|
"Missing arguments! Usage: node ./out/tools/exportDBContents.js exportPath.json.gz",
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Initializing Database...");
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
console.log("Initialized Database.");
|
||||||
|
|
||||||
|
console.log("Getting all destinationProviders...");
|
||||||
|
const destinationProviders = await prisma.desinationProvider.findMany();
|
||||||
|
|
||||||
|
console.log("Getting all forwardRules...");
|
||||||
|
const forwardRules = await prisma.forwardRule.findMany();
|
||||||
|
|
||||||
|
console.log("Getting all permissions...");
|
||||||
|
const allPermissions = await prisma.permission.findMany();
|
||||||
|
|
||||||
|
console.log("Getting all users...");
|
||||||
|
const users = await prisma.user.findMany();
|
||||||
|
|
||||||
|
const masterList = JSON.stringify({
|
||||||
|
destinationProviders,
|
||||||
|
forwardRules,
|
||||||
|
allPermissions,
|
||||||
|
users,
|
||||||
|
});
|
||||||
|
|
||||||
|
const source = new Readable();
|
||||||
|
source.push(masterList);
|
||||||
|
source.push(null);
|
||||||
|
|
||||||
|
const destination = createWriteStream(process.argv[2]);
|
||||||
|
|
||||||
|
pipeline(source, gzip, destination, err => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Failed to compress JSON data:", err);
|
||||||
|
} else {
|
||||||
|
console.log("Sucesfully saved DB contents.");
|
||||||
|
}
|
||||||
|
});
|
22
backend-legacy/tsconfig.json
Normal file
22
backend-legacy/tsconfig.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2020",
|
||||||
|
"module": "es2022",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
|
||||||
|
"outDir": "./out",
|
||||||
|
"rootDir": "./src",
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
10
backend/Dockerfile
Normal file
10
backend/Dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
FROM golang:latest AS build
|
||||||
|
WORKDIR /build
|
||||||
|
COPY . /build
|
||||||
|
RUN bash build.sh
|
||||||
|
FROM busybox:stable AS run
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /build/backends.prod.json /app/backends.json
|
||||||
|
COPY --from=build /build/api/api /app/hermes
|
||||||
|
COPY --from=build /build/sshbackend/sshbackend /app/sshbackend
|
||||||
|
ENTRYPOINT ["/app/hermes", "--backends-path", "/app/backends.json"]
|
|
@ -1,15 +0,0 @@
|
||||||
package backendruntime
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
var (
|
|
||||||
AvailableBackends []*Backend
|
|
||||||
RunningBackends map[uint]*Runtime
|
|
||||||
TempDir string
|
|
||||||
shouldLog bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RunningBackends = make(map[uint]*Runtime)
|
|
||||||
shouldLog = os.Getenv("HERMES_DEVELOPMENT_MODE") != "" || os.Getenv("HERMES_BACKEND_LOGGING_ENABLED") != "" || os.Getenv("HERMES_LOG_LEVEL") == "debug"
|
|
||||||
}
|
|
|
@ -6,47 +6,50 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/backendlauncher"
|
"git.terah.dev/imterah/hermes/backendlauncher"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO TODO TODO(imterah):
|
var (
|
||||||
// This code is a mess. This NEEDS to be rearchitected and refactored to work better. Or at the very least, this code needs to be documented heavily.
|
AvailableBackends []*Backend
|
||||||
|
RunningBackends map[uint]*Runtime
|
||||||
|
TempDir string
|
||||||
|
)
|
||||||
|
|
||||||
func handleCommand(command interface{}, sock net.Conn, rtcChan chan interface{}) error {
|
func init() {
|
||||||
bytes, err := commonbackend.Marshal(command)
|
RunningBackends = make(map[uint]*Runtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCommand(commandType string, command interface{}, sock net.Conn, rtcChan chan interface{}) {
|
||||||
|
bytes, err := commonbackend.Marshal(commandType, command)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to marshal message: %s", err.Error())
|
log.Warnf("Failed to marshal message: %s", err.Error())
|
||||||
rtcChan <- fmt.Errorf("failed to marshal message: %s", err.Error())
|
rtcChan <- fmt.Errorf("failed to marshal message: %s", err.Error())
|
||||||
|
|
||||||
return fmt.Errorf("failed to marshal message: %s", err.Error())
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sock.Write(bytes); err != nil {
|
if _, err := sock.Write(bytes); err != nil {
|
||||||
log.Warnf("Failed to write message: %s", err.Error())
|
log.Warnf("Failed to write message: %s", err.Error())
|
||||||
rtcChan <- fmt.Errorf("failed to write message: %s", err.Error())
|
rtcChan <- fmt.Errorf("failed to write message: %s", err.Error())
|
||||||
|
|
||||||
return fmt.Errorf("failed to write message: %s", err.Error())
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := commonbackend.Unmarshal(sock)
|
_, data, err := commonbackend.Unmarshal(sock)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to unmarshal message: %s", err.Error())
|
log.Warnf("Failed to unmarshal message: %s", err.Error())
|
||||||
rtcChan <- fmt.Errorf("failed to unmarshal message: %s", err.Error())
|
rtcChan <- fmt.Errorf("failed to unmarshal message: %s", err.Error())
|
||||||
|
|
||||||
return fmt.Errorf("failed to unmarshal message: %s", err.Error())
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rtcChan <- data
|
rtcChan <- data
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runtime *Runtime) goRoutineHandler() error {
|
func (runtime *Runtime) goRoutineHandler() error {
|
||||||
|
@ -66,7 +69,7 @@ func (runtime *Runtime) goRoutineHandler() error {
|
||||||
log.Debugf("Acquired unix socket at: %s", sockPath)
|
log.Debugf("Acquired unix socket at: %s", sockPath)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Debug("Created new Goroutine for socket connection handling")
|
log.Debug("Creating new goroutine for socket connection handling")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
log.Debug("Waiting for Unix socket connections...")
|
log.Debug("Waiting for Unix socket connections...")
|
||||||
|
@ -77,122 +80,56 @@ func (runtime *Runtime) goRoutineHandler() error {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Recieved connection. Attempting to figure out backend state...")
|
log.Debug("Recieved connection. Initializing...")
|
||||||
|
|
||||||
timeoutChannel := time.After(500 * time.Millisecond)
|
defer sock.Close()
|
||||||
|
|
||||||
select {
|
|
||||||
case <-timeoutChannel:
|
|
||||||
log.Debug("Timeout reached. Assuming backend is running.")
|
|
||||||
case hasRestarted, ok := <-runtime.processRestartNotification:
|
|
||||||
if !ok {
|
|
||||||
log.Warnf("Failed to get the process restart notification state!")
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasRestarted {
|
|
||||||
if runtime.OnCrashCallback == nil {
|
|
||||||
log.Warn("The backend has restarted for some reason, but we could not run the on crash callback as the callback is not set!")
|
|
||||||
} else {
|
|
||||||
log.Debug("We have restarted. Running the restart callback...")
|
|
||||||
runtime.OnCrashCallback(sock)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Clearing caches...")
|
|
||||||
runtime.cleanUpPendingCommandProcessingJobs()
|
|
||||||
runtime.messageBufferLock = sync.Mutex{}
|
|
||||||
} else {
|
|
||||||
log.Debug("We have not restarted.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Debug("Setting up Hermes keepalive Goroutine")
|
|
||||||
hasFailedBackendRunningCheckAlready := false
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if !runtime.isRuntimeRunning {
|
commandRaw := <-runtime.RuntimeCommands
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asking for the backend status seems to be a "good-enough" keepalive system. Plus, it provides useful telemetry.
|
log.Debug("Got message from server")
|
||||||
// There isn't a ping command in the backend API, so we have to make do with what we have.
|
|
||||||
//
|
|
||||||
// To be safe here, we have to use the proper (yet annoying) facilities to prevent cross-talk, since we're in
|
|
||||||
// a goroutine, and can't talk directly. This actually has benefits, as the OuterLoop should exit on its own, if we
|
|
||||||
// encounter a critical error.
|
|
||||||
statusResponse, err := runtime.ProcessCommand(&commonbackend.BackendStatusRequest{})
|
|
||||||
|
|
||||||
if err != nil {
|
switch command := commandRaw.(type) {
|
||||||
log.Warnf("Failed to get response for backend (in backend runtime keep alive): %s", err.Error())
|
case *commonbackend.AddProxy:
|
||||||
log.Debugf("Attempting to close socket...")
|
handleCommand("addProxy", command, sock, runtime.RuntimeCommands)
|
||||||
err := sock.Close()
|
case *commonbackend.BackendStatusRequest:
|
||||||
|
handleCommand("backendStatusRequest", command, sock, runtime.RuntimeCommands)
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Failed to close socket: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := statusResponse.(type) {
|
|
||||||
case *commonbackend.BackendStatusResponse:
|
case *commonbackend.BackendStatusResponse:
|
||||||
if !responseMessage.IsRunning {
|
handleCommand("backendStatusResponse", command, sock, runtime.RuntimeCommands)
|
||||||
if hasFailedBackendRunningCheckAlready {
|
case *commonbackend.CheckClientParameters:
|
||||||
if responseMessage.Message != "" {
|
handleCommand("checkClientParameters", command, sock, runtime.RuntimeCommands)
|
||||||
log.Warnf("Backend (in backend keepalive) is up but not active: %s", responseMessage.Message)
|
case *commonbackend.CheckParametersResponse:
|
||||||
} else {
|
handleCommand("checkParametersResponse", command, sock, runtime.RuntimeCommands)
|
||||||
log.Warnf("Backend (in backend keepalive) is up but not active")
|
case *commonbackend.CheckServerParameters:
|
||||||
}
|
handleCommand("checkServerParameters", command, sock, runtime.RuntimeCommands)
|
||||||
}
|
case *commonbackend.ProxyClientConnection:
|
||||||
|
handleCommand("proxyClientConnection", command, sock, runtime.RuntimeCommands)
|
||||||
hasFailedBackendRunningCheckAlready = true
|
case *commonbackend.ProxyConnectionsRequest:
|
||||||
}
|
handleCommand("proxyConnectionsRequest", command, sock, runtime.RuntimeCommands)
|
||||||
|
case *commonbackend.ProxyConnectionsResponse:
|
||||||
|
handleCommand("proxyConnectionsResponse", command, sock, runtime.RuntimeCommands)
|
||||||
|
case *commonbackend.ProxyInstanceResponse:
|
||||||
|
handleCommand("proxyInstanceResponse", command, sock, runtime.RuntimeCommands)
|
||||||
|
case *commonbackend.ProxyInstanceRequest:
|
||||||
|
handleCommand("proxyInstanceRequest", command, sock, runtime.RuntimeCommands)
|
||||||
|
case *commonbackend.ProxyStatusRequest:
|
||||||
|
handleCommand("proxyStatusRequest", command, sock, runtime.RuntimeCommands)
|
||||||
|
case *commonbackend.ProxyStatusResponse:
|
||||||
|
handleCommand("proxyStatusResponse", command, sock, runtime.RuntimeCommands)
|
||||||
|
case *commonbackend.RemoveProxy:
|
||||||
|
handleCommand("removeProxy", command, sock, runtime.RuntimeCommands)
|
||||||
|
case *commonbackend.Start:
|
||||||
|
handleCommand("start", command, sock, runtime.RuntimeCommands)
|
||||||
|
case *commonbackend.Stop:
|
||||||
|
handleCommand("stop", command, sock, runtime.RuntimeCommands)
|
||||||
default:
|
default:
|
||||||
log.Errorf("Got illegal response type for backend (in backend keepalive): %T", responseMessage)
|
log.Warnf("Recieved unknown command type from channel: %q", command)
|
||||||
log.Debugf("Attempting to close socket...")
|
runtime.RuntimeCommands <- fmt.Errorf("unknown command recieved")
|
||||||
err := sock.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Failed to close socket: %s", err.Error())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
OuterLoop:
|
|
||||||
for {
|
|
||||||
_ = <-runtime.startProcessingNotification
|
|
||||||
runtime.isRuntimeCurrentlyProcessing = true
|
|
||||||
|
|
||||||
for chanIndex, messageData := range runtime.messageBuffer {
|
|
||||||
if messageData == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err := handleCommand(messageData.Message, sock, messageData.Channel)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to handle command in backend runtime instance: %s", err.Error())
|
|
||||||
|
|
||||||
if strings.HasPrefix(err.Error(), "failed to write message") {
|
|
||||||
break OuterLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.messageBuffer[chanIndex] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.isRuntimeCurrentlyProcessing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
sock.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
runtime.processRestartNotification <- false
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
log.Debug("Starting process...")
|
log.Debug("Starting process...")
|
||||||
|
|
||||||
|
@ -224,14 +161,6 @@ func (runtime *Runtime) goRoutineHandler() error {
|
||||||
|
|
||||||
log.Debug("Sleeping 5 seconds, and then restarting process")
|
log.Debug("Sleeping 5 seconds, and then restarting process")
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
// NOTE(imterah): This could cause hangs if we're not careful. If the process dies so much that we can't keep up, it should deserve to be hung, really.
|
|
||||||
// There's probably a better way to do this, but this works.
|
|
||||||
//
|
|
||||||
// If this does turn out to be a problem, just increase the Goroutine buffer size.
|
|
||||||
runtime.processRestartNotification <- true
|
|
||||||
|
|
||||||
log.Debug("Sent off notification.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,11 +169,7 @@ func (runtime *Runtime) Start() error {
|
||||||
return fmt.Errorf("runtime already running")
|
return fmt.Errorf("runtime already running")
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.messageBuffer = make([]*messageForBuf, 10)
|
runtime.RuntimeCommands = make(chan interface{})
|
||||||
runtime.messageBufferLock = sync.Mutex{}
|
|
||||||
|
|
||||||
runtime.startProcessingNotification = make(chan bool)
|
|
||||||
runtime.processRestartNotification = make(chan bool, 1)
|
|
||||||
|
|
||||||
runtime.logger = &writeLogger{
|
runtime.logger = &writeLogger{
|
||||||
Runtime: runtime,
|
Runtime: runtime,
|
||||||
|
@ -292,90 +217,6 @@ func (runtime *Runtime) Stop() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runtime *Runtime) ProcessCommand(command interface{}) (interface{}, error) {
|
|
||||||
schedulingAttempts := 0
|
|
||||||
var commandChannel chan interface{}
|
|
||||||
|
|
||||||
SchedulingLoop:
|
|
||||||
for {
|
|
||||||
if !runtime.isRuntimeRunning {
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if schedulingAttempts > 50 {
|
|
||||||
return nil, fmt.Errorf("failed to schedule message transmission after 50 tries (REPORT THIS ISSUE)")
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.messageBufferLock.Lock()
|
|
||||||
|
|
||||||
// Attempt to find spot in buffer to schedule message transmission
|
|
||||||
for i, message := range runtime.messageBuffer {
|
|
||||||
if message != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
commandChannel = make(chan interface{})
|
|
||||||
|
|
||||||
runtime.messageBuffer[i] = &messageForBuf{
|
|
||||||
Channel: commandChannel,
|
|
||||||
Message: command,
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.messageBufferLock.Unlock()
|
|
||||||
break SchedulingLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.messageBufferLock.Unlock()
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
|
|
||||||
schedulingAttempts++
|
|
||||||
}
|
|
||||||
|
|
||||||
if !runtime.isRuntimeCurrentlyProcessing {
|
|
||||||
runtime.startProcessingNotification <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch response and close Channel
|
|
||||||
response, ok := <-commandChannel
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("failed to read from command channel: recieved signal that is not OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
close(commandChannel)
|
|
||||||
|
|
||||||
err, ok := response.(error)
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (runtime *Runtime) cleanUpPendingCommandProcessingJobs() {
|
|
||||||
for messageIndex, message := range runtime.messageBuffer {
|
|
||||||
if message == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
timeoutChannel := time.After(100 * time.Millisecond)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-timeoutChannel:
|
|
||||||
log.Warn("Message channel is likely running (timed out reading from it without an error)")
|
|
||||||
close(message.Channel)
|
|
||||||
case _, ok := <-message.Channel:
|
|
||||||
if ok {
|
|
||||||
log.Warn("Message channel is running, but should be stopped (since message is NOT nil!)")
|
|
||||||
close(message.Channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.messageBuffer[messageIndex] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBackend(path string) *Runtime {
|
func NewBackend(path string) *Runtime {
|
||||||
return &Runtime{
|
return &Runtime{
|
||||||
ProcessPath: path,
|
ProcessPath: path,
|
||||||
|
|
|
@ -4,9 +4,6 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Backend struct {
|
type Backend struct {
|
||||||
|
@ -14,28 +11,15 @@ type Backend struct {
|
||||||
Path string `validate:"required"`
|
Path string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type messageForBuf struct {
|
|
||||||
Channel chan interface{}
|
|
||||||
// TODO(imterah): could this be refactored to just be a []byte instead? Look into this
|
|
||||||
Message interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Runtime struct {
|
type Runtime struct {
|
||||||
isRuntimeRunning bool
|
isRuntimeRunning bool
|
||||||
isRuntimeCurrentlyProcessing bool
|
|
||||||
startProcessingNotification chan bool
|
|
||||||
logger *writeLogger
|
logger *writeLogger
|
||||||
currentProcess *exec.Cmd
|
currentProcess *exec.Cmd
|
||||||
currentListener net.Listener
|
currentListener net.Listener
|
||||||
processRestartNotification chan bool
|
|
||||||
|
|
||||||
messageBufferLock sync.Mutex
|
|
||||||
messageBuffer []*messageForBuf
|
|
||||||
|
|
||||||
ProcessPath string
|
ProcessPath string
|
||||||
Logs []string
|
Logs []string
|
||||||
|
RuntimeCommands chan interface{}
|
||||||
OnCrashCallback func(sock net.Conn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type writeLogger struct {
|
type writeLogger struct {
|
||||||
|
@ -44,17 +28,6 @@ type writeLogger struct {
|
||||||
|
|
||||||
func (writer writeLogger) Write(p []byte) (n int, err error) {
|
func (writer writeLogger) Write(p []byte) (n int, err error) {
|
||||||
logSplit := strings.Split(string(p), "\n")
|
logSplit := strings.Split(string(p), "\n")
|
||||||
|
|
||||||
if shouldLog {
|
|
||||||
for _, logLine := range logSplit {
|
|
||||||
if logLine == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("spawned backend logs: " + logLine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Runtime.Logs = append(writer.Runtime.Logs, logSplit...)
|
writer.Runtime.Logs = append(writer.Runtime.Logs, logSplit...)
|
||||||
|
|
||||||
return len(p), err
|
return len(p), err
|
||||||
|
|
293
backend/api/backup.go
Normal file
293
backend/api/backup.go
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Data structures
|
||||||
|
type BackupBackend struct {
|
||||||
|
ID uint `json:"id" validate:"required"`
|
||||||
|
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Backend string `json:"backend" validate:"required"`
|
||||||
|
BackendParameters string `json:"connectionDetails" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackupProxy struct {
|
||||||
|
ID uint `json:"id" validate:"required"`
|
||||||
|
BackendID uint `json:"destProviderID" validate:"required"`
|
||||||
|
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Protocol string `json:"protocol" validate:"required"`
|
||||||
|
SourceIP string `json:"sourceIP" validate:"required"`
|
||||||
|
SourcePort uint16 `json:"sourcePort" validate:"required"`
|
||||||
|
DestinationPort uint16 `json:"destPort" validate:"required"`
|
||||||
|
AutoStart bool `json:"enabled" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackupPermission struct {
|
||||||
|
ID uint `json:"id" validate:"required"`
|
||||||
|
|
||||||
|
PermissionNode string `json:"permission" validate:"required"`
|
||||||
|
HasPermission bool `json:"has" validate:"required"`
|
||||||
|
UserID uint `json:"userID" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackupUser struct {
|
||||||
|
ID uint `json:"id" validate:"required"`
|
||||||
|
|
||||||
|
Email string `json:"email" validate:"required"`
|
||||||
|
Username *string `json:"username"`
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
Password string `json:"password" validate:"required"`
|
||||||
|
IsBot *bool `json:"isRootServiceAccount"`
|
||||||
|
|
||||||
|
Token *string `json:"rootToken" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackupData struct {
|
||||||
|
Backends []*BackupBackend `json:"destinationProviders" validate:"required"`
|
||||||
|
Proxies []*BackupProxy `json:"forwardRules" validate:"required"`
|
||||||
|
Permissions []*BackupPermission `json:"allPermissions" validate:"required"`
|
||||||
|
Users []*BackupUser `json:"users" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// From https://stackoverflow.com/questions/54461423/efficient-way-to-remove-all-non-alphanumeric-characters-from-large-text
|
||||||
|
func stripAllAlphanumeric(s string) string {
|
||||||
|
var result strings.Builder
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
b := s[i]
|
||||||
|
if ('a' <= b && b <= 'z') ||
|
||||||
|
('A' <= b && b <= 'Z') ||
|
||||||
|
('0' <= b && b <= '9') {
|
||||||
|
result.WriteByte(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupRestoreEntrypoint(cCtx *cli.Context) error {
|
||||||
|
log.Info("Decompressing backup...")
|
||||||
|
|
||||||
|
backupFile, err := os.Open(cCtx.String("backup-path"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open backup: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := gzip.NewReader(backupFile)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize Gzip (compression) reader: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
backupDataBytes, err := io.ReadAll(reader)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read backup contents: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Decompressed backup. Cleaning up...")
|
||||||
|
|
||||||
|
err = reader.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to close Gzip reader: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = backupFile.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to close backup: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Parsing backup into internal structures...")
|
||||||
|
|
||||||
|
backupData := &BackupData{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(backupDataBytes, backupData)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse backup: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validator.New().Struct(backupData); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate backup: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Initializing database and opening it...")
|
||||||
|
|
||||||
|
err = dbcore.InitializeDatabase(&gorm.Config{})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize database: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Running database migrations...")
|
||||||
|
|
||||||
|
if err := dbcore.DoDatabaseMigrations(dbcore.DB); err != nil {
|
||||||
|
return fmt.Errorf("Failed to run database migrations: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Restoring database...")
|
||||||
|
bestEffortOwnerUIDFromBackup := -1
|
||||||
|
|
||||||
|
log.Info("Attempting to find user to use as owner of resources...")
|
||||||
|
|
||||||
|
for _, user := range backupData.Users {
|
||||||
|
foundUser := false
|
||||||
|
failedAdministrationCheck := false
|
||||||
|
|
||||||
|
for _, permission := range backupData.Permissions {
|
||||||
|
if permission.UserID != user.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
foundUser = true
|
||||||
|
|
||||||
|
if !strings.HasPrefix(permission.PermissionNode, "routes.") && permission.PermissionNode != "permissions.see" && !permission.HasPermission {
|
||||||
|
log.Infof("User with email '%s' and ID of '%d' failed administration check (lacks all permissions required). Attempting to find better user", user.Email, user.ID)
|
||||||
|
failedAdministrationCheck = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundUser {
|
||||||
|
log.Warnf("User with email '%s' and ID of '%d' lacks any permissions!", user.Email, user.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if failedAdministrationCheck {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Using user with email '%s', and ID of '%d'", user.Email, user.ID)
|
||||||
|
bestEffortOwnerUIDFromBackup = int(user.ID)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if bestEffortOwnerUIDFromBackup == -1 {
|
||||||
|
log.Warnf("Could not find Administrative level user to use as the owner of resources. Using user with email '%s', and ID of '%d'", backupData.Users[0].Email, backupData.Users[0].ID)
|
||||||
|
bestEffortOwnerUIDFromBackup = int(backupData.Users[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bestEffortOwnerUID uint
|
||||||
|
|
||||||
|
for _, user := range backupData.Users {
|
||||||
|
log.Infof("Migrating user with email '%s' and ID of '%d'", user.Email, user.ID)
|
||||||
|
tokens := make([]dbcore.Token, 0)
|
||||||
|
permissions := make([]dbcore.Permission, 0)
|
||||||
|
|
||||||
|
if user.Token != nil {
|
||||||
|
tokens = append(tokens, dbcore.Token{
|
||||||
|
Token: *user.Token,
|
||||||
|
DisableExpiry: true,
|
||||||
|
CreationIPAddr: "127.0.0.1", // We don't know the creation IP address...
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, permission := range backupData.Permissions {
|
||||||
|
if permission.UserID != user.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions = append(permissions, dbcore.Permission{
|
||||||
|
PermissionNode: permission.PermissionNode,
|
||||||
|
HasPermission: permission.HasPermission,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
username := ""
|
||||||
|
|
||||||
|
if user.Username == nil {
|
||||||
|
username = strings.ToLower(stripAllAlphanumeric(user.Email))
|
||||||
|
log.Warnf("User with ID of '%d' doesn't have a username. Derived username from email is '%s' (email is '%s')", user.ID, username, user.Email)
|
||||||
|
} else {
|
||||||
|
username = *user.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
userDatabase := &dbcore.User{
|
||||||
|
Email: user.Email,
|
||||||
|
Username: username,
|
||||||
|
Name: user.Name,
|
||||||
|
Password: base64.StdEncoding.EncodeToString([]byte(user.Password)),
|
||||||
|
IsBot: user.IsBot,
|
||||||
|
|
||||||
|
Tokens: tokens,
|
||||||
|
Permissions: permissions,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dbcore.DB.Create(userDatabase).Error; err != nil {
|
||||||
|
log.Errorf("Failed to create user: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint(bestEffortOwnerUIDFromBackup) == user.ID {
|
||||||
|
bestEffortOwnerUID = userDatabase.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, backend := range backupData.Backends {
|
||||||
|
log.Infof("Migrating backend ID '%d' with name '%s'", backend.ID, backend.Name)
|
||||||
|
|
||||||
|
backendDatabase := &dbcore.Backend{
|
||||||
|
UserID: bestEffortOwnerUID,
|
||||||
|
Name: backend.Name,
|
||||||
|
Description: backend.Description,
|
||||||
|
Backend: backend.Backend,
|
||||||
|
BackendParameters: base64.StdEncoding.EncodeToString([]byte(backend.BackendParameters)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dbcore.DB.Create(backendDatabase).Error; err != nil {
|
||||||
|
log.Errorf("Failed to create backend: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Migrating proxies for backend ID '%d'", backend.ID)
|
||||||
|
|
||||||
|
for _, proxy := range backupData.Proxies {
|
||||||
|
if proxy.BackendID != backend.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Migrating proxy ID '%d' with name '%s'", proxy.ID, proxy.Name)
|
||||||
|
|
||||||
|
proxyDatabase := &dbcore.Proxy{
|
||||||
|
BackendID: backendDatabase.ID,
|
||||||
|
UserID: bestEffortOwnerUID,
|
||||||
|
|
||||||
|
Name: proxy.Name,
|
||||||
|
Description: proxy.Description,
|
||||||
|
Protocol: proxy.Protocol,
|
||||||
|
SourceIP: proxy.SourceIP,
|
||||||
|
SourcePort: proxy.SourcePort,
|
||||||
|
DestinationPort: proxy.DestinationPort,
|
||||||
|
AutoStart: proxy.AutoStart,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dbcore.DB.Create(proxyDatabase).Error; err != nil {
|
||||||
|
log.Errorf("Failed to create proxy: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Successfully upgraded to Hermes from NextNet.")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -6,13 +6,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendCreationRequest struct {
|
type BackendCreationRequest struct {
|
||||||
|
@ -23,8 +24,7 @@ type BackendCreationRequest struct {
|
||||||
BackendParameters interface{} `json:"connectionDetails" validate:"required"`
|
BackendParameters interface{} `json:"connectionDetails" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupCreateBackend(state *state.State) {
|
func CreateBackend(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/backends/create", func(c *gin.Context) {
|
|
||||||
var req BackendCreationRequest
|
var req BackendCreationRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -35,7 +35,7 @@ func SetupCreateBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -43,7 +43,7 @@ func SetupCreateBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -125,12 +125,16 @@ func SetupCreateBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backendParamCheckResponse, err := backend.ProcessCommand(&commonbackend.CheckServerParameters{
|
backend.RuntimeCommands <- &commonbackend.CheckServerParameters{
|
||||||
|
Type: "checkServerParameters",
|
||||||
Arguments: backendParameters,
|
Arguments: backendParameters,
|
||||||
})
|
}
|
||||||
|
|
||||||
if err != nil {
|
backendParamCheckResponse := <-backend.RuntimeCommands
|
||||||
log.Warnf("Failed to get response for backend: %s", err.Error())
|
|
||||||
|
switch responseMessage := backendParamCheckResponse.(type) {
|
||||||
|
case error:
|
||||||
|
log.Warnf("Failed to get response for backend: %s", responseMessage.Error())
|
||||||
|
|
||||||
err = backend.Stop()
|
err = backend.Stop()
|
||||||
|
|
||||||
|
@ -143,9 +147,6 @@ func SetupCreateBackend(state *state.State) {
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := backendParamCheckResponse.(type) {
|
|
||||||
case *commonbackend.CheckParametersResponse:
|
case *commonbackend.CheckParametersResponse:
|
||||||
if responseMessage.InResponseTo != "checkServerParameters" {
|
if responseMessage.InResponseTo != "checkServerParameters" {
|
||||||
log.Errorf("Got illegal response to CheckServerParameters: %s", responseMessage.InResponseTo)
|
log.Errorf("Got illegal response to CheckServerParameters: %s", responseMessage.InResponseTo)
|
||||||
|
@ -190,7 +191,7 @@ func SetupCreateBackend(state *state.State) {
|
||||||
|
|
||||||
log.Info("Passed backend checks successfully")
|
log.Info("Passed backend checks successfully")
|
||||||
|
|
||||||
backendInDatabase := &db.Backend{
|
backendInDatabase := &dbcore.Backend{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
|
@ -198,7 +199,7 @@ func SetupCreateBackend(state *state.State) {
|
||||||
BackendParameters: base64.StdEncoding.EncodeToString(backendParameters),
|
BackendParameters: base64.StdEncoding.EncodeToString(backendParameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := state.DB.DB.Create(&backendInDatabase); result.Error != nil {
|
if result := dbcore.DB.Create(&backendInDatabase); result.Error != nil {
|
||||||
log.Warnf("Failed to create backend: %s", result.Error.Error())
|
log.Warnf("Failed to create backend: %s", result.Error.Error())
|
||||||
|
|
||||||
err = backend.Stop()
|
err = backend.Stop()
|
||||||
|
@ -214,12 +215,16 @@ func SetupCreateBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backendStartResponse, err := backend.ProcessCommand(&commonbackend.Start{
|
backend.RuntimeCommands <- &commonbackend.Start{
|
||||||
|
Type: "start",
|
||||||
Arguments: backendParameters,
|
Arguments: backendParameters,
|
||||||
})
|
}
|
||||||
|
|
||||||
if err != nil {
|
backendStartResponse := <-backend.RuntimeCommands
|
||||||
log.Warnf("Failed to get response for backend: %s", err.Error())
|
|
||||||
|
switch responseMessage := backendStartResponse.(type) {
|
||||||
|
case error:
|
||||||
|
log.Warnf("Failed to get response for backend: %s", responseMessage.Error())
|
||||||
|
|
||||||
err = backend.Stop()
|
err = backend.Stop()
|
||||||
|
|
||||||
|
@ -232,9 +237,6 @@ func SetupCreateBackend(state *state.State) {
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := backendStartResponse.(type) {
|
|
||||||
case *commonbackend.BackendStatusResponse:
|
case *commonbackend.BackendStatusResponse:
|
||||||
if !responseMessage.IsRunning {
|
if !responseMessage.IsRunning {
|
||||||
err = backend.Stop()
|
err = backend.Stop()
|
||||||
|
@ -266,5 +268,4 @@ func SetupCreateBackend(state *state.State) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendLookupRequest struct {
|
type BackendLookupRequest struct {
|
||||||
|
@ -26,9 +27,9 @@ type SanitizedBackend struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
BackendID uint `json:"id"`
|
BackendID uint `json:"id"`
|
||||||
OwnerID uint `json:"ownerID"`
|
OwnerID uint `json:"ownerID"`
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description"`
|
||||||
Backend string `json:"backend"`
|
Backend string `json:"backend"`
|
||||||
BackendParameters *string `json:"connectionDetails,omitempty"`
|
BackendParameters *string `json:"connectionDetails"`
|
||||||
Logs []string `json:"logs"`
|
Logs []string `json:"logs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +38,7 @@ type LookupResponse struct {
|
||||||
Data []*SanitizedBackend `json:"data"`
|
Data []*SanitizedBackend `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupLookupBackend(state *state.State) {
|
func LookupBackend(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/backends/lookup", func(c *gin.Context) {
|
|
||||||
var req BackendLookupRequest
|
var req BackendLookupRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -49,7 +49,7 @@ func SetupLookupBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -57,7 +57,7 @@ func SetupLookupBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -85,7 +85,7 @@ func SetupLookupBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backends := []db.Backend{}
|
backends := []dbcore.Backend{}
|
||||||
queryString := []string{}
|
queryString := []string{}
|
||||||
queryParameters := []interface{}{}
|
queryParameters := []interface{}{}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ func SetupLookupBackend(state *state.State) {
|
||||||
queryParameters = append(queryParameters, req.Backend)
|
queryParameters = append(queryParameters, req.Backend)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&backends).Error; err != nil {
|
if err := dbcore.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&backends).Error; err != nil {
|
||||||
log.Warnf("Failed to get backends: %s", err.Error())
|
log.Warnf("Failed to get backends: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -160,5 +160,4 @@ func SetupLookupBackend(state *state.State) {
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: sanitizedBackends,
|
Data: sanitizedBackends,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendRemovalRequest struct {
|
type BackendRemovalRequest struct {
|
||||||
|
@ -17,8 +18,7 @@ type BackendRemovalRequest struct {
|
||||||
BackendID uint `json:"id" validate:"required"`
|
BackendID uint `json:"id" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupRemoveBackend(state *state.State) {
|
func RemoveBackend(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/backends/remove", func(c *gin.Context) {
|
|
||||||
var req BackendRemovalRequest
|
var req BackendRemovalRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -29,7 +29,7 @@ func SetupRemoveBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -37,7 +37,7 @@ func SetupRemoveBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -65,8 +65,8 @@ func SetupRemoveBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var backend *db.Backend
|
var backend *dbcore.Backend
|
||||||
backendRequest := state.DB.DB.Where("id = ?", req.BackendID).Find(&backend)
|
backendRequest := dbcore.DB.Where("id = ?", req.BackendID).Find(&backend)
|
||||||
|
|
||||||
if backendRequest.Error != nil {
|
if backendRequest.Error != nil {
|
||||||
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error.Error())
|
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error.Error())
|
||||||
|
@ -88,7 +88,7 @@ func SetupRemoveBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Delete(backend).Error; err != nil {
|
if err := dbcore.DB.Delete(backend).Error; err != nil {
|
||||||
log.Warnf("failed to delete backend: %s", err.Error())
|
log.Warnf("failed to delete backend: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -120,5 +120,4 @@ func SetupRemoveBackend(state *state.State) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConnectionsRequest struct {
|
type ConnectionsRequest struct {
|
||||||
|
@ -36,8 +37,7 @@ type ConnectionsResponse struct {
|
||||||
Data []*SanitizedConnection `json:"data"`
|
Data []*SanitizedConnection `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupGetConnections(state *state.State) {
|
func GetConnections(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/forward/connections", func(c *gin.Context) {
|
|
||||||
var req ConnectionsRequest
|
var req ConnectionsRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -48,7 +48,7 @@ func SetupGetConnections(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -56,8 +56,7 @@ func SetupGetConnections(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -84,8 +83,8 @@ func SetupGetConnections(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy db.Proxy
|
var proxy dbcore.Proxy
|
||||||
proxyRequest := state.DB.DB.Where("id = ?", req.Id).First(&proxy)
|
proxyRequest := dbcore.DB.Where("id = ?", req.Id).First(&proxy)
|
||||||
|
|
||||||
if proxyRequest.Error != nil {
|
if proxyRequest.Error != nil {
|
||||||
log.Warnf("failed to find proxy: %s", proxyRequest.Error.Error())
|
log.Warnf("failed to find proxy: %s", proxyRequest.Error.Error())
|
||||||
|
@ -119,19 +118,19 @@ func SetupGetConnections(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backendResponse, err := backendRuntime.ProcessCommand(&commonbackend.ProxyConnectionsRequest{})
|
backendRuntime.RuntimeCommands <- &commonbackend.ProxyConnectionsRequest{
|
||||||
|
Type: "proxyConnectionsRequest",
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
backendResponse := <-backendRuntime.RuntimeCommands
|
||||||
log.Warnf("Failed to get response for backend: %s", err.Error())
|
|
||||||
|
switch responseMessage := backendResponse.(type) {
|
||||||
|
case error:
|
||||||
|
log.Warnf("Failed to get response for backend: %s", responseMessage.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Failed to get status response from backend",
|
"error": "Failed to get status response from backend",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
|
||||||
case *commonbackend.ProxyConnectionsResponse:
|
case *commonbackend.ProxyConnectionsResponse:
|
||||||
sanitizedConnections := []*SanitizedConnection{}
|
sanitizedConnections := []*SanitizedConnection{}
|
||||||
|
|
||||||
|
@ -161,5 +160,4 @@ func SetupGetConnections(state *state.State) {
|
||||||
"error": "Got illegal response type",
|
"error": "Got illegal response type",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyCreationRequest struct {
|
type ProxyCreationRequest struct {
|
||||||
|
@ -25,8 +26,7 @@ type ProxyCreationRequest struct {
|
||||||
AutoStart *bool `json:"autoStart"`
|
AutoStart *bool `json:"autoStart"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupCreateProxy(state *state.State) {
|
func CreateProxy(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/forward/create", func(c *gin.Context) {
|
|
||||||
var req ProxyCreationRequest
|
var req ProxyCreationRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -37,7 +37,7 @@ func SetupCreateProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -45,8 +45,7 @@ func SetupCreateProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -81,8 +80,8 @@ func SetupCreateProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var backend db.Backend
|
var backend dbcore.Backend
|
||||||
backendRequest := state.DB.DB.Where("id = ?", req.ProviderID).First(&backend)
|
backendRequest := dbcore.DB.Where("id = ?", req.ProviderID).First(&backend)
|
||||||
|
|
||||||
if backendRequest.Error != nil {
|
if backendRequest.Error != nil {
|
||||||
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error.Error())
|
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error.Error())
|
||||||
|
@ -106,7 +105,7 @@ func SetupCreateProxy(state *state.State) {
|
||||||
autoStart = *req.AutoStart
|
autoStart = *req.AutoStart
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := &db.Proxy{
|
proxy := &dbcore.Proxy{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
BackendID: req.ProviderID,
|
BackendID: req.ProviderID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
|
@ -118,14 +117,12 @@ func SetupCreateProxy(state *state.State) {
|
||||||
AutoStart: autoStart,
|
AutoStart: autoStart,
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := state.DB.DB.Create(proxy); result.Error != nil {
|
if result := dbcore.DB.Create(proxy); result.Error != nil {
|
||||||
log.Warnf("failed to create proxy: %s", result.Error.Error())
|
log.Warnf("failed to create proxy: %s", result.Error.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Failed to add forward rule to database",
|
"error": "Failed to add forward rule to database",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if autoStart {
|
if autoStart {
|
||||||
|
@ -142,24 +139,25 @@ func SetupCreateProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backendResponse, err := backend.ProcessCommand(&commonbackend.AddProxy{
|
backend.RuntimeCommands <- &commonbackend.AddProxy{
|
||||||
|
Type: "addProxy",
|
||||||
SourceIP: proxy.SourceIP,
|
SourceIP: proxy.SourceIP,
|
||||||
SourcePort: proxy.SourcePort,
|
SourcePort: proxy.SourcePort,
|
||||||
DestPort: proxy.DestinationPort,
|
DestPort: proxy.DestinationPort,
|
||||||
Protocol: proxy.Protocol,
|
Protocol: proxy.Protocol,
|
||||||
})
|
}
|
||||||
|
|
||||||
if err != nil {
|
backendResponse := <-backend.RuntimeCommands
|
||||||
log.Warnf("Failed to get response for backend #%d: %s", proxy.BackendID, err.Error())
|
|
||||||
|
switch responseMessage := backendResponse.(type) {
|
||||||
|
case error:
|
||||||
|
log.Warnf("Failed to get response for backend #%d: %s", proxy.BackendID, responseMessage.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "failed to get response from backend",
|
"error": "failed to get response from backend",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
|
||||||
case *commonbackend.ProxyStatusResponse:
|
case *commonbackend.ProxyStatusResponse:
|
||||||
if !responseMessage.IsActive {
|
if !responseMessage.IsActive {
|
||||||
log.Warnf("Failed to start proxy for backend #%d", proxy.BackendID)
|
log.Warnf("Failed to start proxy for backend #%d", proxy.BackendID)
|
||||||
|
@ -173,5 +171,4 @@ func SetupCreateProxy(state *state.State) {
|
||||||
"success": true,
|
"success": true,
|
||||||
"id": proxy.ID,
|
"id": proxy.ID,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyLookupRequest struct {
|
type ProxyLookupRequest struct {
|
||||||
|
@ -28,7 +29,7 @@ type ProxyLookupRequest struct {
|
||||||
type SanitizedProxy struct {
|
type SanitizedProxy struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description"`
|
||||||
Protcol string `json:"protocol"`
|
Protcol string `json:"protocol"`
|
||||||
SourceIP string `json:"sourceIP"`
|
SourceIP string `json:"sourceIP"`
|
||||||
SourcePort uint16 `json:"sourcePort"`
|
SourcePort uint16 `json:"sourcePort"`
|
||||||
|
@ -42,8 +43,7 @@ type ProxyLookupResponse struct {
|
||||||
Data []*SanitizedProxy `json:"data"`
|
Data []*SanitizedProxy `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupLookupProxy(state *state.State) {
|
func LookupProxy(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/forward/lookup", func(c *gin.Context) {
|
|
||||||
var req ProxyLookupRequest
|
var req ProxyLookupRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -54,7 +54,7 @@ func SetupLookupProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -62,7 +62,7 @@ func SetupLookupProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -95,12 +95,10 @@ func SetupLookupProxy(state *state.State) {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Protocol specified in body must either be 'tcp' or 'udp'",
|
"error": "Protocol specified in body must either be 'tcp' or 'udp'",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies := []db.Proxy{}
|
proxies := []dbcore.Proxy{}
|
||||||
|
|
||||||
queryString := []string{}
|
queryString := []string{}
|
||||||
queryParameters := []interface{}{}
|
queryParameters := []interface{}{}
|
||||||
|
@ -150,7 +148,7 @@ func SetupLookupProxy(state *state.State) {
|
||||||
queryParameters = append(queryParameters, req.Protocol)
|
queryParameters = append(queryParameters, req.Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&proxies).Error; err != nil {
|
if err := dbcore.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&proxies).Error; err != nil {
|
||||||
log.Warnf("failed to get proxies: %s", err.Error())
|
log.Warnf("failed to get proxies: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -180,5 +178,4 @@ func SetupLookupProxy(state *state.State) {
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: sanitizedProxies,
|
Data: sanitizedProxies,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyRemovalRequest struct {
|
type ProxyRemovalRequest struct {
|
||||||
|
@ -18,8 +19,7 @@ type ProxyRemovalRequest struct {
|
||||||
ID uint `validate:"required" json:"id"`
|
ID uint `validate:"required" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupRemoveProxy(state *state.State) {
|
func RemoveProxy(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/forward/remove", func(c *gin.Context) {
|
|
||||||
var req ProxyRemovalRequest
|
var req ProxyRemovalRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -30,7 +30,7 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -38,8 +38,7 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -66,8 +65,8 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy *db.Proxy
|
var proxy *dbcore.Proxy
|
||||||
proxyRequest := state.DB.DB.Where("id = ?", req.ID).Find(&proxy)
|
proxyRequest := dbcore.DB.Where("id = ?", req.ID).Find(&proxy)
|
||||||
|
|
||||||
if proxyRequest.Error != nil {
|
if proxyRequest.Error != nil {
|
||||||
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
||||||
|
@ -89,7 +88,7 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Delete(proxy).Error; err != nil {
|
if err := dbcore.DB.Delete(proxy).Error; err != nil {
|
||||||
log.Warnf("failed to delete proxy: %s", err.Error())
|
log.Warnf("failed to delete proxy: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -111,33 +110,32 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backendResponse, err := backend.ProcessCommand(&commonbackend.RemoveProxy{
|
backend.RuntimeCommands <- &commonbackend.RemoveProxy{
|
||||||
|
Type: "removeProxy",
|
||||||
SourceIP: proxy.SourceIP,
|
SourceIP: proxy.SourceIP,
|
||||||
SourcePort: proxy.SourcePort,
|
SourcePort: proxy.SourcePort,
|
||||||
DestPort: proxy.DestinationPort,
|
DestPort: proxy.DestinationPort,
|
||||||
Protocol: proxy.Protocol,
|
Protocol: proxy.Protocol,
|
||||||
})
|
}
|
||||||
|
|
||||||
if err != nil {
|
backendResponse := <-backend.RuntimeCommands
|
||||||
log.Warnf("Failed to get response for backend #%d: %s", proxy.BackendID, err.Error())
|
|
||||||
|
switch responseMessage := backendResponse.(type) {
|
||||||
|
case error:
|
||||||
|
log.Warnf("Failed to get response for backend #%d: %s", proxy.BackendID, responseMessage.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Failed to get response from backend. Proxy was still successfully deleted",
|
"error": "Failed to get response from backend. Proxy was still successfully deleted",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
|
||||||
case *commonbackend.ProxyStatusResponse:
|
case *commonbackend.ProxyStatusResponse:
|
||||||
if responseMessage.IsActive {
|
if responseMessage.IsActive {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Failed to stop proxy. Proxy was still successfully deleted",
|
"error": "Failed to stop proxy. Proxy was still successfully deleted",
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
return
|
||||||
"success": true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
||||||
|
@ -145,6 +143,11 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyStartRequest struct {
|
type ProxyStartRequest struct {
|
||||||
|
@ -18,8 +19,7 @@ type ProxyStartRequest struct {
|
||||||
ID uint `validate:"required" json:"id"`
|
ID uint `validate:"required" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupStartProxy(state *state.State) {
|
func StartProxy(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/forward/start", func(c *gin.Context) {
|
|
||||||
var req ProxyStartRequest
|
var req ProxyStartRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -30,7 +30,7 @@ func SetupStartProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -38,8 +38,7 @@ func SetupStartProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -66,8 +65,8 @@ func SetupStartProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy *db.Proxy
|
var proxy *dbcore.Proxy
|
||||||
proxyRequest := state.DB.DB.Where("id = ?", req.ID).Find(&proxy)
|
proxyRequest := dbcore.DB.Where("id = ?", req.ID).Find(&proxy)
|
||||||
|
|
||||||
if proxyRequest.Error != nil {
|
if proxyRequest.Error != nil {
|
||||||
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
||||||
|
@ -101,12 +100,15 @@ func SetupStartProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backendResponse, err := backend.ProcessCommand(&commonbackend.AddProxy{
|
backend.RuntimeCommands <- &commonbackend.AddProxy{
|
||||||
|
Type: "addProxy",
|
||||||
SourceIP: proxy.SourceIP,
|
SourceIP: proxy.SourceIP,
|
||||||
SourcePort: proxy.SourcePort,
|
SourcePort: proxy.SourcePort,
|
||||||
DestPort: proxy.DestinationPort,
|
DestPort: proxy.DestinationPort,
|
||||||
Protocol: proxy.Protocol,
|
Protocol: proxy.Protocol,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
backendResponse := <-backend.RuntimeCommands
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
switch responseMessage := backendResponse.(type) {
|
||||||
case error:
|
case error:
|
||||||
|
@ -115,22 +117,29 @@ func SetupStartProxy(state *state.State) {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "failed to get response from backend",
|
"error": "failed to get response from backend",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return
|
||||||
case *commonbackend.ProxyStatusResponse:
|
case *commonbackend.ProxyStatusResponse:
|
||||||
if !responseMessage.IsActive {
|
if !responseMessage.IsActive {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "failed to start proxy",
|
"error": "failed to start proxy",
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
return
|
||||||
"success": true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Got invalid response from backend. Proxy was likely still successfully started",
|
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyStopRequest struct {
|
type ProxyStopRequest struct {
|
||||||
|
@ -18,9 +19,8 @@ type ProxyStopRequest struct {
|
||||||
ID uint `validate:"required" json:"id"`
|
ID uint `validate:"required" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupStopProxy(state *state.State) {
|
func StopProxy(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/forward/stop", func(c *gin.Context) {
|
var req ProxyStopRequest
|
||||||
var req ProxyStartRequest
|
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
@ -30,7 +30,7 @@ func SetupStopProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -38,8 +38,7 @@ func SetupStopProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -66,8 +65,8 @@ func SetupStopProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy *db.Proxy
|
var proxy *dbcore.Proxy
|
||||||
proxyRequest := state.DB.DB.Where("id = ?", req.ID).Find(&proxy)
|
proxyRequest := dbcore.DB.Where("id = ?", req.ID).Find(&proxy)
|
||||||
|
|
||||||
if proxyRequest.Error != nil {
|
if proxyRequest.Error != nil {
|
||||||
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
||||||
|
@ -101,12 +100,15 @@ func SetupStopProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backendResponse, err := backend.ProcessCommand(&commonbackend.RemoveProxy{
|
backend.RuntimeCommands <- &commonbackend.RemoveProxy{
|
||||||
|
Type: "removeProxy",
|
||||||
SourceIP: proxy.SourceIP,
|
SourceIP: proxy.SourceIP,
|
||||||
SourcePort: proxy.SourcePort,
|
SourcePort: proxy.SourcePort,
|
||||||
DestPort: proxy.DestinationPort,
|
DestPort: proxy.DestinationPort,
|
||||||
Protocol: proxy.Protocol,
|
Protocol: proxy.Protocol,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
backendResponse := <-backend.RuntimeCommands
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
switch responseMessage := backendResponse.(type) {
|
||||||
case error:
|
case error:
|
||||||
|
@ -115,22 +117,27 @@ func SetupStopProxy(state *state.State) {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "failed to get response from backend",
|
"error": "failed to get response from backend",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return
|
||||||
case *commonbackend.ProxyStatusResponse:
|
case *commonbackend.ProxyStatusResponse:
|
||||||
if responseMessage.IsActive {
|
if responseMessage.IsActive {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "failed to stop proxy",
|
"error": "failed to stop proxy",
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
return
|
||||||
"success": true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Got invalid response from backend. Proxy was likely still successfully stopped",
|
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"github.com/go-playground/validator/v10"
|
||||||
permissionHelper "git.terah.dev/imterah/hermes/backend/api/permissions"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
|
permissionHelper "git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
@ -20,11 +22,13 @@ type UserCreationRequest struct {
|
||||||
Email string `validate:"required"`
|
Email string `validate:"required"`
|
||||||
Password string `validate:"required"`
|
Password string `validate:"required"`
|
||||||
Username string `validate:"required"`
|
Username string `validate:"required"`
|
||||||
|
|
||||||
|
// TODO: implement support
|
||||||
|
ExistingUserToken string `json:"token"`
|
||||||
IsBot bool
|
IsBot bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupCreateUser(state *state.State) {
|
func CreateUser(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/users/create", func(c *gin.Context) {
|
|
||||||
if !signupEnabled && !unsafeSignup {
|
if !signupEnabled && !unsafeSignup {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
"error": "Signing up is not enabled at this time.",
|
"error": "Signing up is not enabled at this time.",
|
||||||
|
@ -43,7 +47,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -51,8 +55,8 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *db.User
|
var user *dbcore.User
|
||||||
userRequest := state.DB.DB.Where("email = ? OR username = ?", req.Email, req.Username).Find(&user)
|
userRequest := dbcore.DB.Where("email = ? OR username = ?", req.Email, req.Username).Find(&user)
|
||||||
|
|
||||||
if userRequest.Error != nil {
|
if userRequest.Error != nil {
|
||||||
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
||||||
|
@ -86,7 +90,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions := []db.Permission{}
|
permissions := []dbcore.Permission{}
|
||||||
|
|
||||||
for _, permission := range permissionHelper.DefaultPermissionNodes {
|
for _, permission := range permissionHelper.DefaultPermissionNodes {
|
||||||
permissionEnabledState := false
|
permissionEnabledState := false
|
||||||
|
@ -95,7 +99,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
permissionEnabledState = true
|
permissionEnabledState = true
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions = append(permissions, db.Permission{
|
permissions = append(permissions, dbcore.Permission{
|
||||||
PermissionNode: permission,
|
PermissionNode: permission,
|
||||||
HasPermission: permissionEnabledState,
|
HasPermission: permissionEnabledState,
|
||||||
})
|
})
|
||||||
|
@ -113,14 +117,14 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user = &db.User{
|
user = &dbcore.User{
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
Username: req.Username,
|
Username: req.Username,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
IsBot: &req.IsBot,
|
IsBot: &req.IsBot,
|
||||||
Password: base64.StdEncoding.EncodeToString(passwordHashed),
|
Password: base64.StdEncoding.EncodeToString(passwordHashed),
|
||||||
Permissions: permissions,
|
Permissions: permissions,
|
||||||
Tokens: []db.Token{
|
Tokens: []dbcore.Token{
|
||||||
{
|
{
|
||||||
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
DisableExpiry: forceNoExpiryTokens,
|
DisableExpiry: forceNoExpiryTokens,
|
||||||
|
@ -129,7 +133,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := state.DB.DB.Create(&user); result.Error != nil {
|
if result := dbcore.DB.Create(&user); result.Error != nil {
|
||||||
log.Warnf("Failed to create user: %s", result.Error.Error())
|
log.Warnf("Failed to create user: %s", result.Error.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -139,7 +143,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := state.JWT.Generate(user.ID)
|
jwt, err := jwtcore.Generate(user.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to generate JWT: %s", err.Error())
|
log.Warnf("Failed to generate JWT: %s", err.Error())
|
||||||
|
@ -156,5 +160,4 @@ func SetupCreateUser(state *state.State) {
|
||||||
"token": jwt,
|
"token": jwt,
|
||||||
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,8 +21,7 @@ type UserLoginRequest struct {
|
||||||
Password string `validate:"required"`
|
Password string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupLoginUser(state *state.State) {
|
func LoginUser(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/users/login", func(c *gin.Context) {
|
|
||||||
var req UserLoginRequest
|
var req UserLoginRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -32,7 +32,7 @@ func SetupLoginUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -61,8 +61,8 @@ func SetupLoginUser(state *state.State) {
|
||||||
userFindRequest += "username = ?"
|
userFindRequest += "username = ?"
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *db.User
|
var user *dbcore.User
|
||||||
userRequest := state.DB.DB.Where(userFindRequest, userFindRequestArguments...).Find(&user)
|
userRequest := dbcore.DB.Where(userFindRequest, userFindRequestArguments...).Find(&user)
|
||||||
|
|
||||||
if userRequest.Error != nil {
|
if userRequest.Error != nil {
|
||||||
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
||||||
|
@ -119,7 +119,7 @@ func SetupLoginUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := &db.Token{
|
token := &dbcore.Token{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
|
|
||||||
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
|
@ -127,7 +127,7 @@ func SetupLoginUser(state *state.State) {
|
||||||
CreationIPAddr: c.ClientIP(),
|
CreationIPAddr: c.ClientIP(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := state.DB.DB.Create(&token); result.Error != nil {
|
if result := dbcore.DB.Create(&token); result.Error != nil {
|
||||||
log.Warnf("Failed to create user: %s", result.Error.Error())
|
log.Warnf("Failed to create user: %s", result.Error.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -137,7 +137,7 @@ func SetupLoginUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := state.JWT.Generate(user.ID)
|
jwt, err := jwtcore.Generate(user.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to generate JWT: %s", err.Error())
|
log.Warnf("Failed to generate JWT: %s", err.Error())
|
||||||
|
@ -154,5 +154,4 @@ func SetupLoginUser(state *state.State) {
|
||||||
"token": jwt,
|
"token": jwt,
|
||||||
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserLookupRequest struct {
|
type UserLookupRequest struct {
|
||||||
|
@ -34,8 +35,7 @@ type LookupResponse struct {
|
||||||
Data []*SanitizedUsers `json:"data"`
|
Data []*SanitizedUsers `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupLookupUser(state *state.State) {
|
func LookupUser(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/users/lookup", func(c *gin.Context) {
|
|
||||||
var req UserLookupRequest
|
var req UserLookupRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -46,7 +46,7 @@ func SetupLookupUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -54,7 +54,7 @@ func SetupLookupUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -74,7 +74,7 @@ func SetupLookupUser(state *state.State) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
users := []db.User{}
|
users := []dbcore.User{}
|
||||||
queryString := []string{}
|
queryString := []string{}
|
||||||
queryParameters := []interface{}{}
|
queryParameters := []interface{}{}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ func SetupLookupUser(state *state.State) {
|
||||||
queryParameters = append(queryParameters, req.IsBot)
|
queryParameters = append(queryParameters, req.IsBot)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&users).Error; err != nil {
|
if err := dbcore.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&users).Error; err != nil {
|
||||||
log.Warnf("Failed to get users: %s", err.Error())
|
log.Warnf("Failed to get users: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -133,5 +133,4 @@ func SetupLookupUser(state *state.State) {
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: sanitizedUsers,
|
Data: sanitizedUsers,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,18 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRefreshRequest struct {
|
type UserRefreshRequest struct {
|
||||||
Token string `validate:"required"`
|
Token string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupRefreshUserToken(state *state.State) {
|
func RefreshUserToken(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/users/refresh", func(c *gin.Context) {
|
|
||||||
var req UserRefreshRequest
|
var req UserRefreshRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -27,7 +27,7 @@ func SetupRefreshUserToken(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -35,8 +35,8 @@ func SetupRefreshUserToken(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenInDatabase *db.Token
|
var tokenInDatabase *dbcore.Token
|
||||||
tokenRequest := state.DB.DB.Where("token = ?", req.Token).Find(&tokenInDatabase)
|
tokenRequest := dbcore.DB.Where("token = ?", req.Token).Find(&tokenInDatabase)
|
||||||
|
|
||||||
if tokenRequest.Error != nil {
|
if tokenRequest.Error != nil {
|
||||||
log.Warnf("failed to find if token exists or not: %s", tokenRequest.Error.Error())
|
log.Warnf("failed to find if token exists or not: %s", tokenRequest.Error.Error())
|
||||||
|
@ -65,7 +65,7 @@ func SetupRefreshUserToken(state *state.State) {
|
||||||
"error": "Token has expired",
|
"error": "Token has expired",
|
||||||
})
|
})
|
||||||
|
|
||||||
tx := state.DB.DB.Delete(tokenInDatabase)
|
tx := dbcore.DB.Delete(tokenInDatabase)
|
||||||
|
|
||||||
if tx.Error != nil {
|
if tx.Error != nil {
|
||||||
log.Warnf("Failed to delete expired token from database: %s", tx.Error.Error())
|
log.Warnf("Failed to delete expired token from database: %s", tx.Error.Error())
|
||||||
|
@ -75,8 +75,8 @@ func SetupRefreshUserToken(state *state.State) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the user to check if the user exists before doing anything
|
// Get the user to check if the user exists before doing anything
|
||||||
var user *db.User
|
var user *dbcore.User
|
||||||
userRequest := state.DB.DB.Where("id = ?", tokenInDatabase.UserID).Find(&user)
|
userRequest := dbcore.DB.Where("id = ?", tokenInDatabase.UserID).Find(&user)
|
||||||
|
|
||||||
if tokenRequest.Error != nil {
|
if tokenRequest.Error != nil {
|
||||||
log.Warnf("failed to find if token user or not: %s", userRequest.Error.Error())
|
log.Warnf("failed to find if token user or not: %s", userRequest.Error.Error())
|
||||||
|
@ -98,7 +98,7 @@ func SetupRefreshUserToken(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := state.JWT.Generate(user.ID)
|
jwt, err := jwtcore.Generate(user.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to generate JWT: %s", err.Error())
|
log.Warnf("Failed to generate JWT: %s", err.Error())
|
||||||
|
@ -114,5 +114,4 @@ func SetupRefreshUserToken(state *state.State) {
|
||||||
"success": true,
|
"success": true,
|
||||||
"token": jwt,
|
"token": jwt,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRemovalRequest struct {
|
type UserRemovalRequest struct {
|
||||||
|
@ -16,8 +17,7 @@ type UserRemovalRequest struct {
|
||||||
UID *uint `json:"uid"`
|
UID *uint `json:"uid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupRemoveUser(state *state.State) {
|
func RemoveUser(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/users/remove", func(c *gin.Context) {
|
|
||||||
var req UserRemovalRequest
|
var req UserRemovalRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -28,7 +28,7 @@ func SetupRemoveUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -36,7 +36,7 @@ func SetupRemoveUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -73,8 +73,8 @@ func SetupRemoveUser(state *state.State) {
|
||||||
// Make sure the user exists first if we have a custom UserID
|
// Make sure the user exists first if we have a custom UserID
|
||||||
|
|
||||||
if uid != user.ID {
|
if uid != user.ID {
|
||||||
var customUser *db.User
|
var customUser *dbcore.User
|
||||||
userRequest := state.DB.DB.Where("id = ?", uid).Find(customUser)
|
userRequest := dbcore.DB.Where("id = ?", uid).Find(customUser)
|
||||||
|
|
||||||
if userRequest.Error != nil {
|
if userRequest.Error != nil {
|
||||||
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
||||||
|
@ -97,10 +97,9 @@ func SetupRemoveUser(state *state.State) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.DB.DB.Select("Tokens", "Permissions", "Proxys", "Backends").Where("id = ?", uid).Delete(user)
|
dbcore.DB.Select("Tokens", "Permissions", "Proxys", "Backends").Where("id = ?", uid).Delete(user)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DB struct {
|
|
||||||
DB *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(backend, params string) (*DB, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
dialector, err := initDialector(backend, params)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to initialize physical database: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
database, err := gorm.Open(dialector)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open database: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DB{DB: database}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) DoMigrations() error {
|
|
||||||
if err := db.DB.AutoMigrate(&Proxy{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.DB.AutoMigrate(&Backend{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.DB.AutoMigrate(&Permission{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.DB.AutoMigrate(&Token{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.DB.AutoMigrate(&User{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initDialector(backend, params string) (gorm.Dialector, error) {
|
|
||||||
switch backend {
|
|
||||||
case "sqlite":
|
|
||||||
if params == "" {
|
|
||||||
return nil, fmt.Errorf("sqlite database file not specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
return sqlite.Open(params), nil
|
|
||||||
case "postgresql":
|
|
||||||
if params == "" {
|
|
||||||
return nil, fmt.Errorf("postgres DSN not specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
return postgres.Open(params), nil
|
|
||||||
case "":
|
|
||||||
return nil, fmt.Errorf("no database backend specified in environment variables")
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown database backend specified: %s", os.Getenv(backend))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Backend struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
UserID uint
|
|
||||||
|
|
||||||
Name string
|
|
||||||
Description *string
|
|
||||||
Backend string
|
|
||||||
BackendParameters string
|
|
||||||
|
|
||||||
Proxies []Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
type Proxy struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
BackendID uint
|
|
||||||
UserID uint
|
|
||||||
|
|
||||||
Name string
|
|
||||||
Description *string
|
|
||||||
Protocol string
|
|
||||||
SourceIP string
|
|
||||||
SourcePort uint16
|
|
||||||
DestinationPort uint16
|
|
||||||
AutoStart bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Permission struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
PermissionNode string
|
|
||||||
HasPermission bool
|
|
||||||
UserID uint
|
|
||||||
}
|
|
||||||
|
|
||||||
type Token struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
UserID uint
|
|
||||||
|
|
||||||
Token string
|
|
||||||
DisableExpiry bool
|
|
||||||
CreationIPAddr string
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
Email string `gorm:"unique"`
|
|
||||||
Username string `gorm:"unique"`
|
|
||||||
Name string
|
|
||||||
Password string
|
|
||||||
IsBot *bool
|
|
||||||
|
|
||||||
Permissions []Permission
|
|
||||||
OwnedProxies []Proxy
|
|
||||||
OwnedBackends []Backend
|
|
||||||
Tokens []Token
|
|
||||||
}
|
|
142
backend/api/dbcore/db.go
Normal file
142
backend/api/dbcore/db.go
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package dbcore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Backend struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
UserID uint
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Description *string
|
||||||
|
Backend string
|
||||||
|
BackendParameters string
|
||||||
|
|
||||||
|
Proxies []Proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
BackendID uint
|
||||||
|
UserID uint
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Description *string
|
||||||
|
Protocol string
|
||||||
|
SourceIP string
|
||||||
|
SourcePort uint16
|
||||||
|
DestinationPort uint16
|
||||||
|
AutoStart bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permission struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
PermissionNode string
|
||||||
|
HasPermission bool
|
||||||
|
UserID uint
|
||||||
|
}
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
UserID uint
|
||||||
|
|
||||||
|
Token string
|
||||||
|
DisableExpiry bool
|
||||||
|
CreationIPAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
Email string `gorm:"unique"`
|
||||||
|
Username string `gorm:"unique"`
|
||||||
|
Name string
|
||||||
|
Password string
|
||||||
|
IsBot *bool
|
||||||
|
|
||||||
|
Permissions []Permission
|
||||||
|
OwnedProxies []Proxy
|
||||||
|
OwnedBackends []Backend
|
||||||
|
Tokens []Token
|
||||||
|
}
|
||||||
|
|
||||||
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
func InitializeDatabaseDialector() (gorm.Dialector, error) {
|
||||||
|
databaseBackend := os.Getenv("HERMES_DATABASE_BACKEND")
|
||||||
|
|
||||||
|
switch databaseBackend {
|
||||||
|
case "sqlite":
|
||||||
|
filePath := os.Getenv("HERMES_SQLITE_FILEPATH")
|
||||||
|
|
||||||
|
if filePath == "" {
|
||||||
|
return nil, fmt.Errorf("sqlite database file not specified (missing HERMES_SQLITE_FILEPATH)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlite.Open(filePath), nil
|
||||||
|
case "postgresql":
|
||||||
|
postgresDSN := os.Getenv("HERMES_POSTGRES_DSN")
|
||||||
|
|
||||||
|
if postgresDSN == "" {
|
||||||
|
return nil, fmt.Errorf("postgres DSN not specified (missing HERMES_POSTGRES_DSN)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return postgres.Open(postgresDSN), nil
|
||||||
|
case "":
|
||||||
|
return nil, fmt.Errorf("no database backend specified in environment variables (missing HERMES_DATABASE_BACKEND)")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown database backend specified: %s", os.Getenv(databaseBackend))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitializeDatabase(config *gorm.Config) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
dialector, err := InitializeDatabaseDialector()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize physical database: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
DB, err = gorm.Open(dialector, config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open database: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoDatabaseMigrations(db *gorm.DB) error {
|
||||||
|
if err := db.AutoMigrate(&Proxy{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.AutoMigrate(&Backend{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.AutoMigrate(&Permission{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.AutoMigrate(&Token{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.AutoMigrate(&User{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
package jwt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DevelopmentModeTimings = time.Duration(60*24) * time.Minute
|
|
||||||
NormalModeTimings = time.Duration(3) * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
type JWTCore struct {
|
|
||||||
Key []byte
|
|
||||||
Database *db.DB
|
|
||||||
TimeMultiplier time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(key []byte, database *db.DB, timeMultiplier time.Duration) *JWTCore {
|
|
||||||
jwtCore := &JWTCore{
|
|
||||||
Key: key,
|
|
||||||
Database: database,
|
|
||||||
TimeMultiplier: timeMultiplier,
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwtCore
|
|
||||||
}
|
|
||||||
|
|
||||||
func (jwtCore *JWTCore) Parse(tokenString string, options ...jwt.ParserOption) (*jwt.Token, error) {
|
|
||||||
return jwt.Parse(tokenString, jwtCore.jwtKeyCallback, options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (jwtCore *JWTCore) GetUserFromJWT(token string) (*db.User, error) {
|
|
||||||
if jwtCore.Database == nil {
|
|
||||||
return nil, fmt.Errorf("database is not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedJWT, err := jwtCore.Parse(token)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
|
||||||
return nil, fmt.Errorf("token is expired")
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
audience, err := parsedJWT.Claims.GetAudience()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(audience) < 1 {
|
|
||||||
return nil, fmt.Errorf("audience is too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
uid, err := strconv.Atoi(audience[0])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &db.User{}
|
|
||||||
userRequest := jwtCore.Database.DB.Preload("Permissions").Where("id = ?", uint(uid)).Find(&user)
|
|
||||||
|
|
||||||
if userRequest.Error != nil {
|
|
||||||
return user, fmt.Errorf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
userExists := userRequest.RowsAffected > 0
|
|
||||||
|
|
||||||
if !userExists {
|
|
||||||
return user, fmt.Errorf("user does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (jwtCore *JWTCore) Generate(uid uint) (string, error) {
|
|
||||||
currentJWTTime := jwt.NewNumericDate(time.Now())
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtCore.TimeMultiplier)),
|
|
||||||
IssuedAt: currentJWTTime,
|
|
||||||
NotBefore: currentJWTTime,
|
|
||||||
// Convert the user ID to a string, and then set it as the audience parameters only value (there's only 1 user per key)
|
|
||||||
Audience: []string{strconv.Itoa(int(uid))},
|
|
||||||
})
|
|
||||||
|
|
||||||
signedToken, err := token.SignedString(jwtCore.Key)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return signedToken, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (jwtCore *JWTCore) jwtKeyCallback(*jwt.Token) (any, error) {
|
|
||||||
return jwtCore.Key, nil
|
|
||||||
}
|
|
117
backend/api/jwtcore/jwt.go
Normal file
117
backend/api/jwtcore/jwt.go
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package jwtcore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
JWTKey []byte
|
||||||
|
developmentMode bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupJWT() error {
|
||||||
|
var err error
|
||||||
|
jwtDataString := os.Getenv("HERMES_JWT_SECRET")
|
||||||
|
|
||||||
|
if jwtDataString == "" {
|
||||||
|
return fmt.Errorf("JWT secret isn't set (missing HERMES_JWT_SECRET)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("HERMES_JWT_BASE64_ENCODED") != "" {
|
||||||
|
JWTKey, err = base64.StdEncoding.DecodeString(jwtDataString)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode base64 JWT: %s", err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
JWTKey = []byte(jwtDataString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("HERMES_DEVELOPMENT_MODE") != "" {
|
||||||
|
developmentMode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(tokenString string, options ...jwt.ParserOption) (*jwt.Token, error) {
|
||||||
|
return jwt.Parse(tokenString, JWTKeyCallback, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserFromJWT(token string) (*dbcore.User, error) {
|
||||||
|
parsedJWT, err := Parse(token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
|
return nil, fmt.Errorf("token is expired")
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audience, err := parsedJWT.Claims.GetAudience()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(audience) < 1 {
|
||||||
|
return nil, fmt.Errorf("audience is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := strconv.Atoi(audience[0])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &dbcore.User{}
|
||||||
|
userRequest := dbcore.DB.Preload("Permissions").Where("id = ?", uint(uid)).Find(&user)
|
||||||
|
|
||||||
|
if userRequest.Error != nil {
|
||||||
|
return user, fmt.Errorf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
userExists := userRequest.RowsAffected > 0
|
||||||
|
|
||||||
|
if !userExists {
|
||||||
|
return user, fmt.Errorf("user does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Generate(uid uint) (string, error) {
|
||||||
|
timeMultiplier := 3
|
||||||
|
|
||||||
|
if developmentMode {
|
||||||
|
timeMultiplier = 60 * 24
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(timeMultiplier) * time.Minute)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||||
|
Audience: []string{strconv.Itoa(int(uid))},
|
||||||
|
})
|
||||||
|
|
||||||
|
signedToken, err := token.SignedString(JWTKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func JWTKeyCallback(*jwt.Token) (interface{}, error) {
|
||||||
|
return JWTKey, nil
|
||||||
|
}
|
|
@ -4,24 +4,22 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/controllers/v1/backends"
|
"git.terah.dev/imterah/hermes/api/controllers/v1/backends"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/controllers/v1/proxies"
|
"git.terah.dev/imterah/hermes/api/controllers/v1/proxies"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/controllers/v1/users"
|
"git.terah.dev/imterah/hermes/api/controllers/v1/users"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwt"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func apiEntrypoint(cCtx *cli.Context) error {
|
func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
@ -35,26 +33,7 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
log.Info("Hermes is initializing...")
|
log.Info("Hermes is initializing...")
|
||||||
log.Debug("Initializing database and opening it...")
|
log.Debug("Initializing database and opening it...")
|
||||||
|
|
||||||
databaseBackendName := os.Getenv("HERMES_DATABASE_BACKEND")
|
err := dbcore.InitializeDatabase(&gorm.Config{})
|
||||||
var databaseBackendParams string
|
|
||||||
|
|
||||||
if databaseBackendName == "sqlite" {
|
|
||||||
databaseBackendParams = os.Getenv("HERMES_SQLITE_FILEPATH")
|
|
||||||
|
|
||||||
if databaseBackendParams == "" {
|
|
||||||
log.Fatal("HERMES_SQLITE_FILEPATH is not set")
|
|
||||||
}
|
|
||||||
} else if databaseBackendName == "postgresql" {
|
|
||||||
databaseBackendParams = os.Getenv("HERMES_POSTGRES_DSN")
|
|
||||||
|
|
||||||
if databaseBackendParams == "" {
|
|
||||||
log.Fatal("HERMES_POSTGRES_DSN is not set")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Fatalf("Unsupported database backend: %s", databaseBackendName)
|
|
||||||
}
|
|
||||||
|
|
||||||
dbInstance, err := db.New(databaseBackendName, databaseBackendParams)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize database: %s", err)
|
log.Fatalf("Failed to initialize database: %s", err)
|
||||||
|
@ -62,38 +41,16 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Debug("Running database migrations...")
|
log.Debug("Running database migrations...")
|
||||||
|
|
||||||
if err := dbInstance.DoMigrations(); err != nil {
|
if err := dbcore.DoDatabaseMigrations(dbcore.DB); err != nil {
|
||||||
return fmt.Errorf("Failed to run database migrations: %s", err)
|
return fmt.Errorf("Failed to run database migrations: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Initializing the JWT subsystem...")
|
log.Debug("Initializing the JWT subsystem...")
|
||||||
|
|
||||||
jwtDataString := os.Getenv("HERMES_JWT_SECRET")
|
if err := jwtcore.SetupJWT(); err != nil {
|
||||||
var jwtKey []byte
|
return fmt.Errorf("Failed to initialize the JWT subsystem: %s", err.Error())
|
||||||
var jwtValidityTimeDuration time.Duration
|
|
||||||
|
|
||||||
if jwtDataString == "" {
|
|
||||||
log.Fatalf("HERMES_JWT_SECRET is not set")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("HERMES_JWT_BASE64_ENCODED") != "" {
|
|
||||||
jwtKey, err = base64.StdEncoding.DecodeString(jwtDataString)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to decode base64 JWT: %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
jwtKey = []byte(jwtDataString)
|
|
||||||
}
|
|
||||||
|
|
||||||
if developmentMode {
|
|
||||||
jwtValidityTimeDuration = jwt.DevelopmentModeTimings
|
|
||||||
} else {
|
|
||||||
jwtValidityTimeDuration = jwt.NormalModeTimings
|
|
||||||
}
|
|
||||||
|
|
||||||
jwtInstance := jwt.New(jwtKey, dbInstance, jwtValidityTimeDuration)
|
|
||||||
|
|
||||||
log.Debug("Initializing the backend subsystem...")
|
log.Debug("Initializing the backend subsystem...")
|
||||||
|
|
||||||
backendMetadataPath := cCtx.String("backends-path")
|
backendMetadataPath := cCtx.String("backends-path")
|
||||||
|
@ -118,9 +75,9 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Debug("Enumerating backends...")
|
log.Debug("Enumerating backends...")
|
||||||
|
|
||||||
backendList := []db.Backend{}
|
backendList := []dbcore.Backend{}
|
||||||
|
|
||||||
if err := dbInstance.DB.Find(&backendList).Error; err != nil {
|
if err := dbcore.DB.Find(&backendList).Error; err != nil {
|
||||||
return fmt.Errorf("Failed to enumerate backends: %s", err.Error())
|
return fmt.Errorf("Failed to enumerate backends: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,94 +98,6 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
backendInstance := backendruntime.NewBackend(backendRuntimeFilePath)
|
backendInstance := backendruntime.NewBackend(backendRuntimeFilePath)
|
||||||
|
|
||||||
backendInstance.OnCrashCallback = func(conn net.Conn) {
|
|
||||||
backendParameters, err := base64.StdEncoding.DecodeString(backend.BackendParameters)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to decode backend parameters for backend #%d: %s", backend.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
marshalledStartCommand, err := commonbackend.Marshal(&commonbackend.Start{
|
|
||||||
Arguments: backendParameters,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to marshal start command for backend #%d: %s", backend.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := conn.Write(marshalledStartCommand); err != nil {
|
|
||||||
log.Errorf("Failed to send start command for backend #%d: %s", backend.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
backendResponse, err := commonbackend.Unmarshal(conn)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to get start command response for backend #%d: %s", backend.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
|
||||||
case *commonbackend.BackendStatusResponse:
|
|
||||||
if !responseMessage.IsRunning {
|
|
||||||
log.Errorf("Failed to start backend #%d: %s", backend.ID, responseMessage.Message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Backend #%d has been reinitialized successfully", backend.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Warnf("Backend #%d has reinitialized! Starting up auto-starting proxies...", backend.ID)
|
|
||||||
|
|
||||||
autoStartProxies := []db.Proxy{}
|
|
||||||
|
|
||||||
if err := dbInstance.DB.Where("backend_id = ? AND auto_start = true", backend.ID).Find(&autoStartProxies).Error; err != nil {
|
|
||||||
log.Errorf("Failed to query proxies to autostart: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, proxy := range autoStartProxies {
|
|
||||||
log.Infof("Starting up route #%d for backend #%d: %s", proxy.ID, backend.ID, proxy.Name)
|
|
||||||
|
|
||||||
marhalledCommand, err := commonbackend.Marshal(&commonbackend.AddProxy{
|
|
||||||
SourceIP: proxy.SourceIP,
|
|
||||||
SourcePort: proxy.SourcePort,
|
|
||||||
DestPort: proxy.DestinationPort,
|
|
||||||
Protocol: proxy.Protocol,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to marshal proxy adding request for backend #%d and route #%d: %s", proxy.BackendID, proxy.ID, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := conn.Write(marhalledCommand); err != nil {
|
|
||||||
log.Errorf("Failed to send proxy adding request for backend #%d and route #%d: %s", proxy.BackendID, proxy.ID, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
backendResponse, err := commonbackend.Unmarshal(conn)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to get response for backend #%d and route #%d: %s", proxy.BackendID, proxy.ID, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
|
||||||
case *commonbackend.ProxyStatusResponse:
|
|
||||||
if !responseMessage.IsActive {
|
|
||||||
log.Warnf("Failed to start proxy for backend #%d and route #%d", proxy.BackendID, proxy.ID)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Errorf("Got illegal response type for backend #%d and proxy #%d: %T", proxy.BackendID, proxy.ID, responseMessage)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = backendInstance.Start()
|
err = backendInstance.Start()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -243,12 +112,16 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
backendStartResponse, err := backendInstance.ProcessCommand(&commonbackend.Start{
|
backendInstance.RuntimeCommands <- &commonbackend.Start{
|
||||||
|
Type: "start",
|
||||||
Arguments: backendParameters,
|
Arguments: backendParameters,
|
||||||
})
|
}
|
||||||
|
|
||||||
if err != nil {
|
backendStartResponse := <-backendInstance.RuntimeCommands
|
||||||
log.Warnf("Failed to get response for backend #%d: %s", backend.ID, err.Error())
|
|
||||||
|
switch responseMessage := backendStartResponse.(type) {
|
||||||
|
case error:
|
||||||
|
log.Warnf("Failed to get response for backend #%d: %s", backend.ID, responseMessage.Error())
|
||||||
|
|
||||||
err = backendInstance.Stop()
|
err = backendInstance.Stop()
|
||||||
|
|
||||||
|
@ -257,9 +130,6 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := backendStartResponse.(type) {
|
|
||||||
case *commonbackend.BackendStatusResponse:
|
case *commonbackend.BackendStatusResponse:
|
||||||
if !responseMessage.IsRunning {
|
if !responseMessage.IsRunning {
|
||||||
err = backendInstance.Stop()
|
err = backendInstance.Stop()
|
||||||
|
@ -285,9 +155,9 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Infof("Successfully initialized backend #%d", backend.ID)
|
log.Infof("Successfully initialized backend #%d", backend.ID)
|
||||||
|
|
||||||
autoStartProxies := []db.Proxy{}
|
autoStartProxies := []dbcore.Proxy{}
|
||||||
|
|
||||||
if err := dbInstance.DB.Where("backend_id = ? AND auto_start = true", backend.ID).Find(&autoStartProxies).Error; err != nil {
|
if err := dbcore.DB.Where("backend_id = ? AND auto_start = true", backend.ID).Find(&autoStartProxies).Error; err != nil {
|
||||||
log.Errorf("Failed to query proxies to autostart: %s", err.Error())
|
log.Errorf("Failed to query proxies to autostart: %s", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -295,19 +165,20 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
for _, proxy := range autoStartProxies {
|
for _, proxy := range autoStartProxies {
|
||||||
log.Infof("Starting up route #%d for backend #%d: %s", proxy.ID, backend.ID, proxy.Name)
|
log.Infof("Starting up route #%d for backend #%d: %s", proxy.ID, backend.ID, proxy.Name)
|
||||||
|
|
||||||
backendResponse, err := backendInstance.ProcessCommand(&commonbackend.AddProxy{
|
backendInstance.RuntimeCommands <- &commonbackend.AddProxy{
|
||||||
|
Type: "addProxy",
|
||||||
SourceIP: proxy.SourceIP,
|
SourceIP: proxy.SourceIP,
|
||||||
SourcePort: proxy.SourcePort,
|
SourcePort: proxy.SourcePort,
|
||||||
DestPort: proxy.DestinationPort,
|
DestPort: proxy.DestinationPort,
|
||||||
Protocol: proxy.Protocol,
|
Protocol: proxy.Protocol,
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to get response for backend #%d and route #%d: %s", proxy.BackendID, proxy.ID, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backendResponse := <-backendInstance.RuntimeCommands
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
switch responseMessage := backendResponse.(type) {
|
||||||
|
case error:
|
||||||
|
log.Errorf("Failed to get response for backend #%d and route #%d: %s", proxy.BackendID, proxy.ID, responseMessage.Error())
|
||||||
|
continue
|
||||||
case *commonbackend.ProxyStatusResponse:
|
case *commonbackend.ProxyStatusResponse:
|
||||||
if !responseMessage.IsActive {
|
if !responseMessage.IsActive {
|
||||||
log.Warnf("Failed to start proxy for backend #%d and route #%d", proxy.BackendID, proxy.ID)
|
log.Warnf("Failed to start proxy for backend #%d and route #%d", proxy.BackendID, proxy.ID)
|
||||||
|
@ -351,25 +222,23 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
engine.SetTrustedProxies(nil)
|
engine.SetTrustedProxies(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
state := state.New(dbInstance, jwtInstance, engine)
|
|
||||||
|
|
||||||
// Initialize routes
|
// Initialize routes
|
||||||
users.SetupCreateUser(state)
|
engine.POST("/api/v1/users/create", users.CreateUser)
|
||||||
users.SetupLoginUser(state)
|
engine.POST("/api/v1/users/login", users.LoginUser)
|
||||||
users.SetupRefreshUserToken(state)
|
engine.POST("/api/v1/users/refresh", users.RefreshUserToken)
|
||||||
users.SetupRemoveUser(state)
|
engine.POST("/api/v1/users/remove", users.RemoveUser)
|
||||||
users.SetupLookupUser(state)
|
engine.POST("/api/v1/users/lookup", users.LookupUser)
|
||||||
|
|
||||||
backends.SetupCreateBackend(state)
|
engine.POST("/api/v1/backends/create", backends.CreateBackend)
|
||||||
backends.SetupRemoveBackend(state)
|
engine.POST("/api/v1/backends/remove", backends.RemoveBackend)
|
||||||
backends.SetupLookupBackend(state)
|
engine.POST("/api/v1/backends/lookup", backends.LookupBackend)
|
||||||
|
|
||||||
proxies.SetupCreateProxy(state)
|
engine.POST("/api/v1/forward/create", proxies.CreateProxy)
|
||||||
proxies.SetupRemoveProxy(state)
|
engine.POST("/api/v1/forward/lookup", proxies.LookupProxy)
|
||||||
proxies.SetupLookupProxy(state)
|
engine.POST("/api/v1/forward/remove", proxies.RemoveProxy)
|
||||||
proxies.SetupStartProxy(state)
|
engine.POST("/api/v1/forward/start", proxies.StartProxy)
|
||||||
proxies.SetupStopProxy(state)
|
engine.POST("/api/v1/forward/stop", proxies.StopProxy)
|
||||||
proxies.SetupGetConnections(state)
|
engine.POST("/api/v1/forward/connections", proxies.GetConnections)
|
||||||
|
|
||||||
log.Infof("Listening on '%s'", listeningAddress)
|
log.Infof("Listening on '%s'", listeningAddress)
|
||||||
err = engine.Run(listeningAddress)
|
err = engine.Run(listeningAddress)
|
||||||
|
@ -406,6 +275,22 @@ func main() {
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
Name: "hermes",
|
Name: "hermes",
|
||||||
Usage: "port forwarding across boundaries",
|
Usage: "port forwarding across boundaries",
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "import",
|
||||||
|
Usage: "imports from legacy NextNet/Hermes source",
|
||||||
|
Aliases: []string{"i"},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "backup-path",
|
||||||
|
Aliases: []string{"bp"},
|
||||||
|
Usage: "path to the backup file",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: backupRestoreEntrypoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "backends-path",
|
Name: "backends-path",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package permissions
|
package permissions
|
||||||
|
|
||||||
import "git.terah.dev/imterah/hermes/backend/api/db"
|
import "git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
|
|
||||||
var DefaultPermissionNodes []string = []string{
|
var DefaultPermissionNodes []string = []string{
|
||||||
"routes.add",
|
"routes.add",
|
||||||
|
@ -27,7 +27,7 @@ var DefaultPermissionNodes []string = []string{
|
||||||
"users.edit",
|
"users.edit",
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserHasPermission(user *db.User, node string) bool {
|
func UserHasPermission(user *dbcore.User, node string) bool {
|
||||||
for _, permission := range user.Permissions {
|
for _, permission := range user.Permissions {
|
||||||
if permission.PermissionNode == node && permission.HasPermission {
|
if permission.PermissionNode == node && permission.HasPermission {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
package state
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwt"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
|
||||||
|
|
||||||
type State struct {
|
|
||||||
DB *db.DB
|
|
||||||
JWT *jwt.JWTCore
|
|
||||||
Engine *gin.Engine
|
|
||||||
Validator *validator.Validate
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(db *db.DB, jwt *jwt.JWTCore, engine *gin.Engine) *State {
|
|
||||||
return &State{
|
|
||||||
DB: db,
|
|
||||||
JWT: jwt,
|
|
||||||
Engine: engine,
|
|
||||||
Validator: validator.New(),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,10 +3,6 @@
|
||||||
"name": "ssh",
|
"name": "ssh",
|
||||||
"path": "./sshbackend/sshbackend"
|
"path": "./sshbackend/sshbackend"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "sshapp",
|
|
||||||
"path": "./sshappbackend/local-code/sshappbackend"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "dummy",
|
"name": "dummy",
|
||||||
"path": "./dummybackend/dummybackend"
|
"path": "./dummybackend/dummybackend"
|
||||||
|
|
|
@ -2,9 +2,5 @@
|
||||||
{
|
{
|
||||||
"name": "ssh",
|
"name": "ssh",
|
||||||
"path": "./sshbackend"
|
"path": "./sshbackend"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sshapp",
|
|
||||||
"path": "./sshappbackend"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package backendutil
|
package backendutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,14 +18,9 @@ type BackendApplicationHelper struct {
|
||||||
|
|
||||||
func (helper *BackendApplicationHelper) Start() error {
|
func (helper *BackendApplicationHelper) Start() error {
|
||||||
log.Debug("BackendApplicationHelper is starting")
|
log.Debug("BackendApplicationHelper is starting")
|
||||||
err := ConfigureProfiling()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Currently waiting for Unix socket connection...")
|
log.Debug("Currently waiting for Unix socket connection...")
|
||||||
|
|
||||||
|
var err error
|
||||||
helper.socket, err = net.Dial("unix", helper.SocketPath)
|
helper.socket, err = net.Dial("unix", helper.SocketPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -34,15 +30,21 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
log.Debug("Sucessfully connected")
|
log.Debug("Sucessfully connected")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
commandRaw, err := commonbackend.Unmarshal(helper.socket)
|
commandType, commandRaw, err := commonbackend.Unmarshal(helper.socket)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch command := commandRaw.(type) {
|
switch commandType {
|
||||||
case *commonbackend.Start:
|
case "start":
|
||||||
ok, err := helper.Backend.StartBackend(command.Arguments)
|
command, ok := commandRaw.(*commonbackend.Start)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err = helper.Backend.StartBackend(command.Arguments)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
message string
|
message string
|
||||||
|
@ -57,12 +59,13 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &commonbackend.BackendStatusResponse{
|
response := &commonbackend.BackendStatusResponse{
|
||||||
|
Type: "backendStatusResponse",
|
||||||
IsRunning: ok,
|
IsRunning: ok,
|
||||||
StatusCode: statusCode,
|
StatusCode: statusCode,
|
||||||
Message: message,
|
Message: message,
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMarshalled, err := commonbackend.Marshal(response)
|
responseMarshalled, err := commonbackend.Marshal(response.Type, response)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
log.Error("failed to marshal response: %s", err.Error())
|
||||||
|
@ -70,37 +73,14 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
helper.socket.Write(responseMarshalled)
|
||||||
case *commonbackend.BackendStatusRequest:
|
case "stop":
|
||||||
ok, err := helper.Backend.GetBackendStatus()
|
_, ok := commandRaw.(*commonbackend.Stop)
|
||||||
|
|
||||||
var (
|
if !ok {
|
||||||
message string
|
return fmt.Errorf("failed to typecast")
|
||||||
statusCode int
|
}
|
||||||
)
|
|
||||||
|
ok, err = helper.Backend.StopBackend()
|
||||||
if err != nil {
|
|
||||||
message = err.Error()
|
|
||||||
statusCode = commonbackend.StatusFailure
|
|
||||||
} else {
|
|
||||||
statusCode = commonbackend.StatusSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &commonbackend.BackendStatusResponse{
|
|
||||||
IsRunning: ok,
|
|
||||||
StatusCode: statusCode,
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
|
|
||||||
responseMarshalled, err := commonbackend.Marshal(response)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
|
||||||
case *commonbackend.Stop:
|
|
||||||
ok, err := helper.Backend.StopBackend()
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
message string
|
message string
|
||||||
|
@ -115,12 +95,13 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &commonbackend.BackendStatusResponse{
|
response := &commonbackend.BackendStatusResponse{
|
||||||
|
Type: "backendStatusResponse",
|
||||||
IsRunning: !ok,
|
IsRunning: !ok,
|
||||||
StatusCode: statusCode,
|
StatusCode: statusCode,
|
||||||
Message: message,
|
Message: message,
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMarshalled, err := commonbackend.Marshal(response)
|
responseMarshalled, err := commonbackend.Marshal(response.Type, response)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
log.Error("failed to marshal response: %s", err.Error())
|
||||||
|
@ -128,19 +109,26 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
helper.socket.Write(responseMarshalled)
|
||||||
case *commonbackend.AddProxy:
|
case "addProxy":
|
||||||
ok, err := helper.Backend.StartProxy(command)
|
command, ok := commandRaw.(*commonbackend.AddProxy)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err = helper.Backend.StartProxy(command)
|
||||||
var hasAnyFailed bool
|
var hasAnyFailed bool
|
||||||
|
|
||||||
if err != nil {
|
if !ok {
|
||||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): %s", command.SourceIP, command.SourcePort, command.DestPort, err.Error())
|
|
||||||
hasAnyFailed = true
|
|
||||||
} else if !ok {
|
|
||||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): StartProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
log.Warnf("failed to add proxy (%s:%d -> remote:%d): StartProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
||||||
hasAnyFailed = true
|
hasAnyFailed = true
|
||||||
|
} else if err != nil {
|
||||||
|
log.Warnf("failed to add proxy (%s:%d -> remote:%d): %s", command.SourceIP, command.SourcePort, command.DestPort, err.Error())
|
||||||
|
hasAnyFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &commonbackend.ProxyStatusResponse{
|
response := &commonbackend.ProxyStatusResponse{
|
||||||
|
Type: "proxyStatusResponse",
|
||||||
SourceIP: command.SourceIP,
|
SourceIP: command.SourceIP,
|
||||||
SourcePort: command.SourcePort,
|
SourcePort: command.SourcePort,
|
||||||
DestPort: command.DestPort,
|
DestPort: command.DestPort,
|
||||||
|
@ -148,7 +136,7 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
IsActive: !hasAnyFailed,
|
IsActive: !hasAnyFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMarshalled, err := commonbackend.Marshal(response)
|
responseMarshalled, err := commonbackend.Marshal(response.Type, response)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
log.Error("failed to marshal response: %s", err.Error())
|
||||||
|
@ -156,19 +144,26 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
helper.socket.Write(responseMarshalled)
|
||||||
case *commonbackend.RemoveProxy:
|
case "removeProxy":
|
||||||
ok, err := helper.Backend.StopProxy(command)
|
command, ok := commandRaw.(*commonbackend.RemoveProxy)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err = helper.Backend.StopProxy(command)
|
||||||
var hasAnyFailed bool
|
var hasAnyFailed bool
|
||||||
|
|
||||||
if err != nil {
|
if !ok {
|
||||||
log.Warnf("failed to remove proxy (%s:%d -> remote:%d): %s", command.SourceIP, command.SourcePort, command.DestPort, err.Error())
|
|
||||||
hasAnyFailed = true
|
|
||||||
} else if !ok {
|
|
||||||
log.Warnf("failed to remove proxy (%s:%d -> remote:%d): RemoveProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
log.Warnf("failed to remove proxy (%s:%d -> remote:%d): RemoveProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
||||||
hasAnyFailed = true
|
hasAnyFailed = true
|
||||||
|
} else if err != nil {
|
||||||
|
log.Warnf("failed to remove proxy (%s:%d -> remote:%d): %s", command.SourceIP, command.SourcePort, command.DestPort, err.Error())
|
||||||
|
hasAnyFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &commonbackend.ProxyStatusResponse{
|
response := &commonbackend.ProxyStatusResponse{
|
||||||
|
Type: "proxyStatusResponse",
|
||||||
SourceIP: command.SourceIP,
|
SourceIP: command.SourceIP,
|
||||||
SourcePort: command.SourcePort,
|
SourcePort: command.SourcePort,
|
||||||
DestPort: command.DestPort,
|
DestPort: command.DestPort,
|
||||||
|
@ -176,7 +171,7 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
IsActive: hasAnyFailed,
|
IsActive: hasAnyFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
responseMarshalled, err := commonbackend.Marshal(response)
|
responseMarshalled, err := commonbackend.Marshal(response.Type, response)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
log.Error("failed to marshal response: %s", err.Error())
|
||||||
|
@ -184,14 +179,21 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
helper.socket.Write(responseMarshalled)
|
||||||
case *commonbackend.ProxyConnectionsRequest:
|
case "proxyConnectionsRequest":
|
||||||
|
_, ok := commandRaw.(*commonbackend.ProxyConnectionsRequest)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
connections := helper.Backend.GetAllClientConnections()
|
connections := helper.Backend.GetAllClientConnections()
|
||||||
|
|
||||||
serverParams := &commonbackend.ProxyConnectionsResponse{
|
serverParams := &commonbackend.ProxyConnectionsResponse{
|
||||||
|
Type: "proxyConnectionsResponse",
|
||||||
Connections: connections,
|
Connections: connections,
|
||||||
}
|
}
|
||||||
|
|
||||||
byteData, err := commonbackend.Marshal(serverParams)
|
byteData, err := commonbackend.Marshal(serverParams.Type, serverParams)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -200,11 +202,18 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
if _, err = helper.socket.Write(byteData); err != nil {
|
if _, err = helper.socket.Write(byteData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case *commonbackend.CheckClientParameters:
|
case "checkClientParameters":
|
||||||
|
command, ok := commandRaw.(*commonbackend.CheckClientParameters)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
resp := helper.Backend.CheckParametersForConnections(command)
|
resp := helper.Backend.CheckParametersForConnections(command)
|
||||||
|
resp.Type = "checkParametersResponse"
|
||||||
resp.InResponseTo = "checkClientParameters"
|
resp.InResponseTo = "checkClientParameters"
|
||||||
|
|
||||||
byteData, err := commonbackend.Marshal(resp)
|
byteData, err := commonbackend.Marshal(resp.Type, resp)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -213,11 +222,18 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
if _, err = helper.socket.Write(byteData); err != nil {
|
if _, err = helper.socket.Write(byteData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case *commonbackend.CheckServerParameters:
|
case "checkServerParameters":
|
||||||
|
command, ok := commandRaw.(*commonbackend.CheckServerParameters)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
resp := helper.Backend.CheckParametersForBackend(command.Arguments)
|
resp := helper.Backend.CheckParametersForBackend(command.Arguments)
|
||||||
|
resp.Type = "checkParametersResponse"
|
||||||
resp.InResponseTo = "checkServerParameters"
|
resp.InResponseTo = "checkServerParameters"
|
||||||
|
|
||||||
byteData, err := commonbackend.Marshal(resp)
|
byteData, err := commonbackend.Marshal(resp.Type, resp)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -226,8 +242,6 @@ func (helper *BackendApplicationHelper) Start() error {
|
||||||
if _, err = helper.socket.Write(byteData); err != nil {
|
if _, err = helper.socket.Write(byteData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
log.Warnf("Unsupported command recieved: %T", command)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
//go:build !debug
|
|
||||||
|
|
||||||
package backendutil
|
|
||||||
|
|
||||||
var endProfileFunc func()
|
|
||||||
|
|
||||||
func ConfigureProfiling() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
//go:build debug
|
|
||||||
|
|
||||||
package backendutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"runtime/pprof"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
"golang.org/x/exp/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ConfigureProfiling() error {
|
|
||||||
profilingMode, err := os.ReadFile("/tmp/hermes.backendlauncher.profilebackends")
|
|
||||||
|
|
||||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch string(profilingMode) {
|
|
||||||
case "cpu":
|
|
||||||
log.Debug("Starting CPU profiling as a background task")
|
|
||||||
go doCPUProfiling()
|
|
||||||
case "mem":
|
|
||||||
log.Debug("Starting memory profiling as a background task")
|
|
||||||
go doMemoryProfiling()
|
|
||||||
default:
|
|
||||||
log.Warnf("Unknown profiling mode: %s", string(profilingMode))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func doCPUProfiling() {
|
|
||||||
// (imterah) WTF? why isn't this being seeded on its own? according to Go docs, this should be seeded automatically...
|
|
||||||
rand.Seed(uint64(time.Now().UnixNano()))
|
|
||||||
|
|
||||||
profileFileName := fmt.Sprintf("/tmp/hermes.backendlauncher.cpu.prof.%d", rand.Int())
|
|
||||||
profileFile, err := os.Create(profileFileName)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create CPU profiling file: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Writing CPU usage profile to '%s'. Will capture when Ctrl+C/SIGTERM is recieved.", profileFileName)
|
|
||||||
pprof.StartCPUProfile(profileFile)
|
|
||||||
|
|
||||||
exitNotification := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(exitNotification, os.Interrupt, syscall.SIGTERM)
|
|
||||||
<-exitNotification
|
|
||||||
|
|
||||||
log.Debug("Recieved SIGTERM. Cleaning up and exiting...")
|
|
||||||
|
|
||||||
pprof.StopCPUProfile()
|
|
||||||
profileFile.Close()
|
|
||||||
|
|
||||||
log.Debug("Exiting...")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doMemoryProfiling() {
|
|
||||||
// (imterah) WTF? why isn't this being seeded on its own? according to Go docs, this should be seeded automatically...
|
|
||||||
rand.Seed(uint64(time.Now().UnixNano()))
|
|
||||||
|
|
||||||
profileFileName := fmt.Sprintf("/tmp/hermes.backendlauncher.mem.prof.%d", rand.Int())
|
|
||||||
profileFile, err := os.Create(profileFileName)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create memory profiling file: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Writing memory profile to '%s'. Will capture when Ctrl+C/SIGTERM is recieved.", profileFileName)
|
|
||||||
|
|
||||||
exitNotification := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(exitNotification, os.Interrupt, syscall.SIGTERM)
|
|
||||||
<-exitNotification
|
|
||||||
|
|
||||||
log.Debug("Recieved SIGTERM. Cleaning up and exiting...")
|
|
||||||
|
|
||||||
pprof.WriteHeapProfile(profileFile)
|
|
||||||
profileFile.Close()
|
|
||||||
|
|
||||||
log.Debug("Exiting...")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
|
@ -1,11 +1,10 @@
|
||||||
package backendutil
|
package backendutil
|
||||||
|
|
||||||
import "git.terah.dev/imterah/hermes/backend/commonbackend"
|
import "git.terah.dev/imterah/hermes/commonbackend"
|
||||||
|
|
||||||
type BackendInterface interface {
|
type BackendInterface interface {
|
||||||
StartBackend(arguments []byte) (bool, error)
|
StartBackend(arguments []byte) (bool, error)
|
||||||
StopBackend() (bool, error)
|
StopBackend() (bool, error)
|
||||||
GetBackendStatus() (bool, error)
|
|
||||||
StartProxy(command *commonbackend.AddProxy) (bool, error)
|
StartProxy(command *commonbackend.AddProxy) (bool, error)
|
||||||
StopProxy(command *commonbackend.RemoveProxy) (bool, error)
|
StopProxy(command *commonbackend.RemoveProxy) (bool, error)
|
||||||
GetAllClientConnections() []*commonbackend.ProxyClientConnection
|
GetAllClientConnections() []*commonbackend.ProxyClientConnection
|
||||||
|
|
|
@ -1,43 +1,20 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
pushd sshbackend > /dev/null
|
pushd sshbackend
|
||||||
echo "building sshbackend"
|
CGO_ENABLED=0 GOOS=linux go build .
|
||||||
go build -ldflags="-s -w" -trimpath .
|
strip sshbackend
|
||||||
popd > /dev/null
|
popd
|
||||||
|
|
||||||
pushd dummybackend > /dev/null
|
pushd dummybackend
|
||||||
echo "building dummybackend"
|
CGO_ENABLED=0 GOOS=linux go build .
|
||||||
go build -ldflags="-s -w" -trimpath .
|
strip dummybackend
|
||||||
popd > /dev/null
|
popd
|
||||||
|
|
||||||
pushd externalbackendlauncher > /dev/null
|
pushd externalbackendlauncher
|
||||||
echo "building externalbackendlauncher"
|
go build .
|
||||||
go build -ldflags="-s -w" -trimpath .
|
strip externalbackendlauncher
|
||||||
popd > /dev/null
|
popd
|
||||||
|
|
||||||
if [ ! -d "sshappbackend/local-code/remote-bin" ]; then
|
pushd api
|
||||||
mkdir "sshappbackend/local-code/remote-bin"
|
CGO_ENABLED=0 GOOS=linux go build .
|
||||||
fi
|
strip api
|
||||||
|
popd
|
||||||
pushd sshappbackend/remote-code > /dev/null
|
|
||||||
echo "building sshappbackend/remote-code"
|
|
||||||
# Disable dynamic linking by disabling CGo.
|
|
||||||
# We need to make the remote code as generic as possible, so we do this
|
|
||||||
echo " - building for arm64"
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o ../local-code/remote-bin/rt-arm64 .
|
|
||||||
echo " - building for arm"
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags="-s -w" -trimpath -o ../local-code/remote-bin/rt-arm .
|
|
||||||
echo " - building for amd64"
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o ../local-code/remote-bin/rt-amd64 .
|
|
||||||
echo " - building for i386"
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags="-s -w" -trimpath -o ../local-code/remote-bin/rt-386 .
|
|
||||||
popd > /dev/null
|
|
||||||
|
|
||||||
pushd sshappbackend/local-code > /dev/null
|
|
||||||
echo "building sshappbackend/local-code"
|
|
||||||
go build -ldflags="-s -w" -trimpath -o sshappbackend .
|
|
||||||
popd > /dev/null
|
|
||||||
|
|
||||||
pushd api > /dev/null
|
|
||||||
echo "building api"
|
|
||||||
go build -ldflags="-s -w" -trimpath .
|
|
||||||
popd > /dev/null
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
package commonbackend
|
package commonbackend
|
||||||
|
|
||||||
type Start struct {
|
type Start struct {
|
||||||
|
Type string // Will be 'start' always
|
||||||
Arguments []byte
|
Arguments []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stop struct {
|
type Stop struct {
|
||||||
|
Type string // Will be 'stop' always
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddProxy struct {
|
type AddProxy struct {
|
||||||
|
Type string // Will be 'addProxy' always
|
||||||
SourceIP string
|
SourceIP string
|
||||||
SourcePort uint16
|
SourcePort uint16
|
||||||
DestPort uint16
|
DestPort uint16
|
||||||
|
@ -15,6 +18,7 @@ type AddProxy struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoveProxy struct {
|
type RemoveProxy struct {
|
||||||
|
Type string // Will be 'removeProxy' always
|
||||||
SourceIP string
|
SourceIP string
|
||||||
SourcePort uint16
|
SourcePort uint16
|
||||||
DestPort uint16
|
DestPort uint16
|
||||||
|
@ -22,6 +26,7 @@ type RemoveProxy struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyStatusRequest struct {
|
type ProxyStatusRequest struct {
|
||||||
|
Type string // Will be 'proxyStatusRequest' always
|
||||||
SourceIP string
|
SourceIP string
|
||||||
SourcePort uint16
|
SourcePort uint16
|
||||||
DestPort uint16
|
DestPort uint16
|
||||||
|
@ -29,6 +34,7 @@ type ProxyStatusRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyStatusResponse struct {
|
type ProxyStatusResponse struct {
|
||||||
|
Type string // Will be 'proxyStatusResponse' always
|
||||||
SourceIP string
|
SourceIP string
|
||||||
SourcePort uint16
|
SourcePort uint16
|
||||||
DestPort uint16
|
DestPort uint16
|
||||||
|
@ -44,22 +50,27 @@ type ProxyInstance struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyInstanceResponse struct {
|
type ProxyInstanceResponse struct {
|
||||||
|
Type string // Will be 'proxyConnectionResponse' always
|
||||||
Proxies []*ProxyInstance // List of connections
|
Proxies []*ProxyInstance // List of connections
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyInstanceRequest struct {
|
type ProxyInstanceRequest struct {
|
||||||
|
Type string // Will be 'proxyConnectionRequest' always
|
||||||
}
|
}
|
||||||
|
|
||||||
type BackendStatusResponse struct {
|
type BackendStatusResponse struct {
|
||||||
|
Type string // Will be 'backendStatusResponse' always
|
||||||
IsRunning bool // True if running, false if not running
|
IsRunning bool // True if running, false if not running
|
||||||
StatusCode int // Either the 'Success' or 'Failure' constant
|
StatusCode int // Either the 'Success' or 'Failure' constant
|
||||||
Message string // String message from the client (ex. failed to dial TCP)
|
Message string // String message from the client (ex. failed to dial TCP)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BackendStatusRequest struct {
|
type BackendStatusRequest struct {
|
||||||
|
Type string // Will be 'backendStatusRequest' always
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyConnectionsRequest struct {
|
type ProxyConnectionsRequest struct {
|
||||||
|
Type string // Will be 'proxyConnectionsRequest' always
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client's connection to a specific proxy
|
// Client's connection to a specific proxy
|
||||||
|
@ -72,10 +83,12 @@ type ProxyClientConnection struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyConnectionsResponse struct {
|
type ProxyConnectionsResponse struct {
|
||||||
|
Type string // Will be 'proxyConnectionsResponse' always
|
||||||
Connections []*ProxyClientConnection // List of connections
|
Connections []*ProxyClientConnection // List of connections
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckClientParameters struct {
|
type CheckClientParameters struct {
|
||||||
|
Type string // Will be 'checkClientParameters' always
|
||||||
SourceIP string
|
SourceIP string
|
||||||
SourcePort uint16
|
SourcePort uint16
|
||||||
DestPort uint16
|
DestPort uint16
|
||||||
|
@ -83,11 +96,13 @@ type CheckClientParameters struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckServerParameters struct {
|
type CheckServerParameters struct {
|
||||||
|
Type string // Will be 'checkServerParameters' always
|
||||||
Arguments []byte
|
Arguments []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sent as a response to either CheckClientParameters or CheckBackendParameters
|
// Sent as a response to either CheckClientParameters or CheckBackendParameters
|
||||||
type CheckParametersResponse struct {
|
type CheckParametersResponse struct {
|
||||||
|
Type string // Will be 'checkParametersResponse' always
|
||||||
InResponseTo string // Will be either 'checkClientParameters' or 'checkServerParameters'
|
InResponseTo string // Will be either 'checkClientParameters' or 'checkServerParameters'
|
||||||
IsValid bool // If true, valid, and if false, invalid
|
IsValid bool // If true, valid, and if false, invalid
|
||||||
Message string // String message from the client (ex. failed to unmarshal JSON: x is not defined)
|
Message string // String message from the client (ex. failed to unmarshal JSON: x is not defined)
|
||||||
|
|
|
@ -84,19 +84,37 @@ func marshalIndividualProxyStruct(conn *ProxyInstance) ([]byte, error) {
|
||||||
return proxyBlock, nil
|
return proxyBlock, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Marshal(command interface{}) ([]byte, error) {
|
func Marshal(commandType string, command interface{}) ([]byte, error) {
|
||||||
switch command := command.(type) {
|
switch commandType {
|
||||||
case *Start:
|
case "start":
|
||||||
startCommandBytes := make([]byte, 1+2+len(command.Arguments))
|
startCommand, ok := command.(*Start)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
startCommandBytes := make([]byte, 1+2+len(startCommand.Arguments))
|
||||||
startCommandBytes[0] = StartID
|
startCommandBytes[0] = StartID
|
||||||
binary.BigEndian.PutUint16(startCommandBytes[1:3], uint16(len(command.Arguments)))
|
binary.BigEndian.PutUint16(startCommandBytes[1:3], uint16(len(startCommand.Arguments)))
|
||||||
copy(startCommandBytes[3:], command.Arguments)
|
copy(startCommandBytes[3:], startCommand.Arguments)
|
||||||
|
|
||||||
return startCommandBytes, nil
|
return startCommandBytes, nil
|
||||||
case *Stop:
|
case "stop":
|
||||||
|
_, ok := command.(*Stop)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
return []byte{StopID}, nil
|
return []byte{StopID}, nil
|
||||||
case *AddProxy:
|
case "addProxy":
|
||||||
sourceIP := net.ParseIP(command.SourceIP)
|
addConnectionCommand, ok := command.(*AddProxy)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceIP := net.ParseIP(addConnectionCommand.SourceIP)
|
||||||
|
|
||||||
var ipVer uint8
|
var ipVer uint8
|
||||||
var ipBytes []byte
|
var ipBytes []byte
|
||||||
|
@ -116,14 +134,14 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
|
|
||||||
copy(addConnectionBytes[2:2+len(ipBytes)], ipBytes)
|
copy(addConnectionBytes[2:2+len(ipBytes)], ipBytes)
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(addConnectionBytes[2+len(ipBytes):4+len(ipBytes)], command.SourcePort)
|
binary.BigEndian.PutUint16(addConnectionBytes[2+len(ipBytes):4+len(ipBytes)], addConnectionCommand.SourcePort)
|
||||||
binary.BigEndian.PutUint16(addConnectionBytes[4+len(ipBytes):6+len(ipBytes)], command.DestPort)
|
binary.BigEndian.PutUint16(addConnectionBytes[4+len(ipBytes):6+len(ipBytes)], addConnectionCommand.DestPort)
|
||||||
|
|
||||||
var protocol uint8
|
var protocol uint8
|
||||||
|
|
||||||
if command.Protocol == "tcp" {
|
if addConnectionCommand.Protocol == "tcp" {
|
||||||
protocol = TCP
|
protocol = TCP
|
||||||
} else if command.Protocol == "udp" {
|
} else if addConnectionCommand.Protocol == "udp" {
|
||||||
protocol = UDP
|
protocol = UDP
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return nil, fmt.Errorf("invalid protocol")
|
||||||
|
@ -132,8 +150,14 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
addConnectionBytes[6+len(ipBytes)] = protocol
|
addConnectionBytes[6+len(ipBytes)] = protocol
|
||||||
|
|
||||||
return addConnectionBytes, nil
|
return addConnectionBytes, nil
|
||||||
case *RemoveProxy:
|
case "removeProxy":
|
||||||
sourceIP := net.ParseIP(command.SourceIP)
|
removeConnectionCommand, ok := command.(*RemoveProxy)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceIP := net.ParseIP(removeConnectionCommand.SourceIP)
|
||||||
|
|
||||||
var ipVer uint8
|
var ipVer uint8
|
||||||
var ipBytes []byte
|
var ipBytes []byte
|
||||||
|
@ -151,14 +175,14 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
removeConnectionBytes[0] = RemoveProxyID
|
removeConnectionBytes[0] = RemoveProxyID
|
||||||
removeConnectionBytes[1] = ipVer
|
removeConnectionBytes[1] = ipVer
|
||||||
copy(removeConnectionBytes[2:2+len(ipBytes)], ipBytes)
|
copy(removeConnectionBytes[2:2+len(ipBytes)], ipBytes)
|
||||||
binary.BigEndian.PutUint16(removeConnectionBytes[2+len(ipBytes):4+len(ipBytes)], command.SourcePort)
|
binary.BigEndian.PutUint16(removeConnectionBytes[2+len(ipBytes):4+len(ipBytes)], removeConnectionCommand.SourcePort)
|
||||||
binary.BigEndian.PutUint16(removeConnectionBytes[4+len(ipBytes):6+len(ipBytes)], command.DestPort)
|
binary.BigEndian.PutUint16(removeConnectionBytes[4+len(ipBytes):6+len(ipBytes)], removeConnectionCommand.DestPort)
|
||||||
|
|
||||||
var protocol uint8
|
var protocol uint8
|
||||||
|
|
||||||
if command.Protocol == "tcp" {
|
if removeConnectionCommand.Protocol == "tcp" {
|
||||||
protocol = TCP
|
protocol = TCP
|
||||||
} else if command.Protocol == "udp" {
|
} else if removeConnectionCommand.Protocol == "udp" {
|
||||||
protocol = UDP
|
protocol = UDP
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return nil, fmt.Errorf("invalid protocol")
|
||||||
|
@ -167,11 +191,17 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
removeConnectionBytes[6+len(ipBytes)] = protocol
|
removeConnectionBytes[6+len(ipBytes)] = protocol
|
||||||
|
|
||||||
return removeConnectionBytes, nil
|
return removeConnectionBytes, nil
|
||||||
case *ProxyConnectionsResponse:
|
case "proxyConnectionsResponse":
|
||||||
connectionsArray := make([][]byte, len(command.Connections))
|
allConnectionsCommand, ok := command.(*ProxyConnectionsResponse)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionsArray := make([][]byte, len(allConnectionsCommand.Connections))
|
||||||
totalSize := 0
|
totalSize := 0
|
||||||
|
|
||||||
for connIndex, conn := range command.Connections {
|
for connIndex, conn := range allConnectionsCommand.Connections {
|
||||||
connectionsArray[connIndex] = marshalIndividualConnectionStruct(conn)
|
connectionsArray[connIndex] = marshalIndividualConnectionStruct(conn)
|
||||||
totalSize += len(connectionsArray[connIndex]) + 1
|
totalSize += len(connectionsArray[connIndex]) + 1
|
||||||
}
|
}
|
||||||
|
@ -193,8 +223,14 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
|
|
||||||
connectionCommandArray[totalSize] = '\n'
|
connectionCommandArray[totalSize] = '\n'
|
||||||
return connectionCommandArray, nil
|
return connectionCommandArray, nil
|
||||||
case *CheckClientParameters:
|
case "checkClientParameters":
|
||||||
sourceIP := net.ParseIP(command.SourceIP)
|
checkClientCommand, ok := command.(*CheckClientParameters)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceIP := net.ParseIP(checkClientCommand.SourceIP)
|
||||||
|
|
||||||
var ipVer uint8
|
var ipVer uint8
|
||||||
var ipBytes []byte
|
var ipBytes []byte
|
||||||
|
@ -212,14 +248,14 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
checkClientBytes[0] = CheckClientParametersID
|
checkClientBytes[0] = CheckClientParametersID
|
||||||
checkClientBytes[1] = ipVer
|
checkClientBytes[1] = ipVer
|
||||||
copy(checkClientBytes[2:2+len(ipBytes)], ipBytes)
|
copy(checkClientBytes[2:2+len(ipBytes)], ipBytes)
|
||||||
binary.BigEndian.PutUint16(checkClientBytes[2+len(ipBytes):4+len(ipBytes)], command.SourcePort)
|
binary.BigEndian.PutUint16(checkClientBytes[2+len(ipBytes):4+len(ipBytes)], checkClientCommand.SourcePort)
|
||||||
binary.BigEndian.PutUint16(checkClientBytes[4+len(ipBytes):6+len(ipBytes)], command.DestPort)
|
binary.BigEndian.PutUint16(checkClientBytes[4+len(ipBytes):6+len(ipBytes)], checkClientCommand.DestPort)
|
||||||
|
|
||||||
var protocol uint8
|
var protocol uint8
|
||||||
|
|
||||||
if command.Protocol == "tcp" {
|
if checkClientCommand.Protocol == "tcp" {
|
||||||
protocol = TCP
|
protocol = TCP
|
||||||
} else if command.Protocol == "udp" {
|
} else if checkClientCommand.Protocol == "udp" {
|
||||||
protocol = UDP
|
protocol = UDP
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return nil, fmt.Errorf("invalid protocol")
|
||||||
|
@ -228,19 +264,31 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
checkClientBytes[6+len(ipBytes)] = protocol
|
checkClientBytes[6+len(ipBytes)] = protocol
|
||||||
|
|
||||||
return checkClientBytes, nil
|
return checkClientBytes, nil
|
||||||
case *CheckServerParameters:
|
case "checkServerParameters":
|
||||||
serverCommandBytes := make([]byte, 1+2+len(command.Arguments))
|
checkServerCommand, ok := command.(*CheckServerParameters)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
serverCommandBytes := make([]byte, 1+2+len(checkServerCommand.Arguments))
|
||||||
serverCommandBytes[0] = CheckServerParametersID
|
serverCommandBytes[0] = CheckServerParametersID
|
||||||
binary.BigEndian.PutUint16(serverCommandBytes[1:3], uint16(len(command.Arguments)))
|
binary.BigEndian.PutUint16(serverCommandBytes[1:3], uint16(len(checkServerCommand.Arguments)))
|
||||||
copy(serverCommandBytes[3:], command.Arguments)
|
copy(serverCommandBytes[3:], checkServerCommand.Arguments)
|
||||||
|
|
||||||
return serverCommandBytes, nil
|
return serverCommandBytes, nil
|
||||||
case *CheckParametersResponse:
|
case "checkParametersResponse":
|
||||||
|
checkParametersCommand, ok := command.(*CheckParametersResponse)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
var checkMethod uint8
|
var checkMethod uint8
|
||||||
|
|
||||||
if command.InResponseTo == "checkClientParameters" {
|
if checkParametersCommand.InResponseTo == "checkClientParameters" {
|
||||||
checkMethod = CheckClientParametersID
|
checkMethod = CheckClientParametersID
|
||||||
} else if command.InResponseTo == "checkServerParameters" {
|
} else if checkParametersCommand.InResponseTo == "checkServerParameters" {
|
||||||
checkMethod = CheckServerParametersID
|
checkMethod = CheckServerParametersID
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid mode recieved (must be either checkClientParameters or checkServerParameters)")
|
return nil, fmt.Errorf("invalid mode recieved (must be either checkClientParameters or checkServerParameters)")
|
||||||
|
@ -248,50 +296,68 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
|
|
||||||
var isValid uint8
|
var isValid uint8
|
||||||
|
|
||||||
if command.IsValid {
|
if checkParametersCommand.IsValid {
|
||||||
isValid = 1
|
isValid = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
checkResponseBytes := make([]byte, 3+2+len(command.Message))
|
checkResponseBytes := make([]byte, 3+2+len(checkParametersCommand.Message))
|
||||||
checkResponseBytes[0] = CheckParametersResponseID
|
checkResponseBytes[0] = CheckParametersResponseID
|
||||||
checkResponseBytes[1] = checkMethod
|
checkResponseBytes[1] = checkMethod
|
||||||
checkResponseBytes[2] = isValid
|
checkResponseBytes[2] = isValid
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(checkResponseBytes[3:5], uint16(len(command.Message)))
|
binary.BigEndian.PutUint16(checkResponseBytes[3:5], uint16(len(checkParametersCommand.Message)))
|
||||||
|
|
||||||
if len(command.Message) != 0 {
|
if len(checkParametersCommand.Message) != 0 {
|
||||||
copy(checkResponseBytes[5:], []byte(command.Message))
|
copy(checkResponseBytes[5:], []byte(checkParametersCommand.Message))
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkResponseBytes, nil
|
return checkResponseBytes, nil
|
||||||
case *BackendStatusResponse:
|
case "backendStatusResponse":
|
||||||
|
backendStatusResponse, ok := command.(*BackendStatusResponse)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
var isRunning uint8
|
var isRunning uint8
|
||||||
|
|
||||||
if command.IsRunning {
|
if backendStatusResponse.IsRunning {
|
||||||
isRunning = 1
|
isRunning = 1
|
||||||
} else {
|
} else {
|
||||||
isRunning = 0
|
isRunning = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
statusResponseBytes := make([]byte, 3+2+len(command.Message))
|
statusResponseBytes := make([]byte, 3+2+len(backendStatusResponse.Message))
|
||||||
statusResponseBytes[0] = BackendStatusResponseID
|
statusResponseBytes[0] = BackendStatusResponseID
|
||||||
statusResponseBytes[1] = isRunning
|
statusResponseBytes[1] = isRunning
|
||||||
statusResponseBytes[2] = byte(command.StatusCode)
|
statusResponseBytes[2] = byte(backendStatusResponse.StatusCode)
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(statusResponseBytes[3:5], uint16(len(command.Message)))
|
binary.BigEndian.PutUint16(statusResponseBytes[3:5], uint16(len(backendStatusResponse.Message)))
|
||||||
|
|
||||||
if len(command.Message) != 0 {
|
if len(backendStatusResponse.Message) != 0 {
|
||||||
copy(statusResponseBytes[5:], []byte(command.Message))
|
copy(statusResponseBytes[5:], []byte(backendStatusResponse.Message))
|
||||||
}
|
}
|
||||||
|
|
||||||
return statusResponseBytes, nil
|
return statusResponseBytes, nil
|
||||||
case *BackendStatusRequest:
|
case "backendStatusRequest":
|
||||||
statusRequestBytes := make([]byte, 1)
|
_, ok := command.(*BackendStatusRequest)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
statusRequestBytes := make([]byte, 2)
|
||||||
statusRequestBytes[0] = BackendStatusRequestID
|
statusRequestBytes[0] = BackendStatusRequestID
|
||||||
|
|
||||||
return statusRequestBytes, nil
|
return statusRequestBytes, nil
|
||||||
case *ProxyStatusRequest:
|
case "proxyStatusRequest":
|
||||||
sourceIP := net.ParseIP(command.SourceIP)
|
proxyStatusRequest, ok := command.(*ProxyStatusRequest)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceIP := net.ParseIP(proxyStatusRequest.SourceIP)
|
||||||
|
|
||||||
var ipVer uint8
|
var ipVer uint8
|
||||||
var ipBytes []byte
|
var ipBytes []byte
|
||||||
|
@ -304,31 +370,37 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
ipVer = IPv4
|
ipVer = IPv4
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBytes := make([]byte, 1+1+len(ipBytes)+2+2+1)
|
proxyStatusRequestBytes := make([]byte, 1+1+len(ipBytes)+2+2+1)
|
||||||
|
|
||||||
commandBytes[0] = ProxyStatusRequestID
|
proxyStatusRequestBytes[0] = ProxyStatusRequestID
|
||||||
commandBytes[1] = ipVer
|
proxyStatusRequestBytes[1] = ipVer
|
||||||
|
|
||||||
copy(commandBytes[2:2+len(ipBytes)], ipBytes)
|
copy(proxyStatusRequestBytes[2:2+len(ipBytes)], ipBytes)
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(commandBytes[2+len(ipBytes):4+len(ipBytes)], command.SourcePort)
|
binary.BigEndian.PutUint16(proxyStatusRequestBytes[2+len(ipBytes):4+len(ipBytes)], proxyStatusRequest.SourcePort)
|
||||||
binary.BigEndian.PutUint16(commandBytes[4+len(ipBytes):6+len(ipBytes)], command.DestPort)
|
binary.BigEndian.PutUint16(proxyStatusRequestBytes[4+len(ipBytes):6+len(ipBytes)], proxyStatusRequest.DestPort)
|
||||||
|
|
||||||
var protocol uint8
|
var protocol uint8
|
||||||
|
|
||||||
if command.Protocol == "tcp" {
|
if proxyStatusRequest.Protocol == "tcp" {
|
||||||
protocol = TCP
|
protocol = TCP
|
||||||
} else if command.Protocol == "udp" {
|
} else if proxyStatusRequest.Protocol == "udp" {
|
||||||
protocol = UDP
|
protocol = UDP
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return nil, fmt.Errorf("invalid protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBytes[6+len(ipBytes)] = protocol
|
proxyStatusRequestBytes[6+len(ipBytes)] = protocol
|
||||||
|
|
||||||
return commandBytes, nil
|
return proxyStatusRequestBytes, nil
|
||||||
case *ProxyStatusResponse:
|
case "proxyStatusResponse":
|
||||||
sourceIP := net.ParseIP(command.SourceIP)
|
proxyStatusResponse, ok := command.(*ProxyStatusResponse)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceIP := net.ParseIP(proxyStatusResponse.SourceIP)
|
||||||
|
|
||||||
var ipVer uint8
|
var ipVer uint8
|
||||||
var ipBytes []byte
|
var ipBytes []byte
|
||||||
|
@ -341,44 +413,50 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
ipVer = IPv4
|
ipVer = IPv4
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBytes := make([]byte, 1+1+len(ipBytes)+2+2+1+1)
|
proxyStatusResponseBytes := make([]byte, 1+1+len(ipBytes)+2+2+1+1)
|
||||||
|
|
||||||
commandBytes[0] = ProxyStatusResponseID
|
proxyStatusResponseBytes[0] = ProxyStatusResponseID
|
||||||
commandBytes[1] = ipVer
|
proxyStatusResponseBytes[1] = ipVer
|
||||||
|
|
||||||
copy(commandBytes[2:2+len(ipBytes)], ipBytes)
|
copy(proxyStatusResponseBytes[2:2+len(ipBytes)], ipBytes)
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(commandBytes[2+len(ipBytes):4+len(ipBytes)], command.SourcePort)
|
binary.BigEndian.PutUint16(proxyStatusResponseBytes[2+len(ipBytes):4+len(ipBytes)], proxyStatusResponse.SourcePort)
|
||||||
binary.BigEndian.PutUint16(commandBytes[4+len(ipBytes):6+len(ipBytes)], command.DestPort)
|
binary.BigEndian.PutUint16(proxyStatusResponseBytes[4+len(ipBytes):6+len(ipBytes)], proxyStatusResponse.DestPort)
|
||||||
|
|
||||||
var protocol uint8
|
var protocol uint8
|
||||||
|
|
||||||
if command.Protocol == "tcp" {
|
if proxyStatusResponse.Protocol == "tcp" {
|
||||||
protocol = TCP
|
protocol = TCP
|
||||||
} else if command.Protocol == "udp" {
|
} else if proxyStatusResponse.Protocol == "udp" {
|
||||||
protocol = UDP
|
protocol = UDP
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return nil, fmt.Errorf("invalid protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBytes[6+len(ipBytes)] = protocol
|
proxyStatusResponseBytes[6+len(ipBytes)] = protocol
|
||||||
|
|
||||||
var isActive uint8
|
var isActive uint8
|
||||||
|
|
||||||
if command.IsActive {
|
if proxyStatusResponse.IsActive {
|
||||||
isActive = 1
|
isActive = 1
|
||||||
} else {
|
} else {
|
||||||
isActive = 0
|
isActive = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBytes[7+len(ipBytes)] = isActive
|
proxyStatusResponseBytes[7+len(ipBytes)] = isActive
|
||||||
|
|
||||||
return commandBytes, nil
|
return proxyStatusResponseBytes, nil
|
||||||
case *ProxyInstanceResponse:
|
case "proxyInstanceResponse":
|
||||||
proxyArray := make([][]byte, len(command.Proxies))
|
proxyConectionResponse, ok := command.(*ProxyInstanceResponse)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyArray := make([][]byte, len(proxyConectionResponse.Proxies))
|
||||||
totalSize := 0
|
totalSize := 0
|
||||||
|
|
||||||
for proxyIndex, proxy := range command.Proxies {
|
for proxyIndex, proxy := range proxyConectionResponse.Proxies {
|
||||||
var err error
|
var err error
|
||||||
proxyArray[proxyIndex], err = marshalIndividualProxyStruct(proxy)
|
proxyArray[proxyIndex], err = marshalIndividualProxyStruct(proxy)
|
||||||
|
|
||||||
|
@ -407,11 +485,23 @@ func Marshal(command interface{}) ([]byte, error) {
|
||||||
connectionCommandArray[totalSize] = '\n'
|
connectionCommandArray[totalSize] = '\n'
|
||||||
|
|
||||||
return connectionCommandArray, nil
|
return connectionCommandArray, nil
|
||||||
case *ProxyInstanceRequest:
|
case "proxyInstanceRequest":
|
||||||
|
_, ok := command.(*ProxyInstanceRequest)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
return []byte{ProxyInstanceRequestID}, nil
|
return []byte{ProxyInstanceRequestID}, nil
|
||||||
case *ProxyConnectionsRequest:
|
case "proxyConnectionsRequest":
|
||||||
|
_, ok := command.(*ProxyConnectionsRequest)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to typecast")
|
||||||
|
}
|
||||||
|
|
||||||
return []byte{ProxyConnectionsRequestID}, nil
|
return []byte{ProxyConnectionsRequestID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("couldn't match command type")
|
return nil, fmt.Errorf("couldn't match command name")
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,13 @@ import (
|
||||||
|
|
||||||
var logLevel = os.Getenv("HERMES_LOG_LEVEL")
|
var logLevel = os.Getenv("HERMES_LOG_LEVEL")
|
||||||
|
|
||||||
func TestStart(t *testing.T) {
|
func TestStartCommandMarshalSupport(t *testing.T) {
|
||||||
commandInput := &Start{
|
commandInput := &Start{
|
||||||
|
Type: "start",
|
||||||
Arguments: []byte("Hello from automated testing"),
|
Arguments: []byte("Hello from automated testing"),
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if logLevel == "debug" {
|
if logLevel == "debug" {
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
log.Printf("Generated array contents: %v", commandMarshalled)
|
||||||
|
@ -25,27 +26,39 @@ func TestStart(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*Start)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*Start)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if !bytes.Equal(commandInput.Arguments, commandUnmarshalled.Arguments) {
|
if !bytes.Equal(commandInput.Arguments, commandUnmarshalled.Arguments) {
|
||||||
log.Fatalf("Arguments are not equal (orig: '%s', unmsh: '%s')", string(commandInput.Arguments), string(commandUnmarshalled.Arguments))
|
log.Fatalf("Arguments are not equal (orig: '%s', unmsh: '%s')", string(commandInput.Arguments), string(commandUnmarshalled.Arguments))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStop(t *testing.T) {
|
func TestStopCommandMarshalSupport(t *testing.T) {
|
||||||
commandInput := &Stop{}
|
commandInput := &Stop{
|
||||||
|
Type: "stop",
|
||||||
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if logLevel == "debug" {
|
if logLevel == "debug" {
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
log.Printf("Generated array contents: %v", commandMarshalled)
|
||||||
|
@ -56,28 +69,39 @@ func TestStop(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := commandUnmarshalledRaw.(*Stop)
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*Stop)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddConnection(t *testing.T) {
|
func TestAddConnectionCommandMarshalSupport(t *testing.T) {
|
||||||
commandInput := &AddProxy{
|
commandInput := &AddProxy{
|
||||||
|
Type: "addProxy",
|
||||||
SourceIP: "192.168.0.139",
|
SourceIP: "192.168.0.139",
|
||||||
SourcePort: 19132,
|
SourcePort: 19132,
|
||||||
DestPort: 19132,
|
DestPort: 19132,
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if logLevel == "debug" {
|
if logLevel == "debug" {
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
log.Printf("Generated array contents: %v", commandMarshalled)
|
||||||
|
@ -88,18 +112,28 @@ func TestAddConnection(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*AddProxy)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*AddProxy)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
||||||
|
@ -121,15 +155,16 @@ func TestAddConnection(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveConnection(t *testing.T) {
|
func TestRemoveConnectionCommandMarshalSupport(t *testing.T) {
|
||||||
commandInput := &RemoveProxy{
|
commandInput := &RemoveProxy{
|
||||||
|
Type: "removeProxy",
|
||||||
SourceIP: "192.168.0.139",
|
SourceIP: "192.168.0.139",
|
||||||
SourcePort: 19132,
|
SourcePort: 19132,
|
||||||
DestPort: 19132,
|
DestPort: 19132,
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
|
@ -140,18 +175,28 @@ func TestRemoveConnection(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*RemoveProxy)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*RemoveProxy)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
||||||
|
@ -173,8 +218,9 @@ func TestRemoveConnection(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAllConnections(t *testing.T) {
|
func TestGetAllConnectionsCommandMarshalSupport(t *testing.T) {
|
||||||
commandInput := &ProxyConnectionsResponse{
|
commandInput := &ProxyConnectionsResponse{
|
||||||
|
Type: "proxyConnectionsResponse",
|
||||||
Connections: []*ProxyClientConnection{
|
Connections: []*ProxyClientConnection{
|
||||||
{
|
{
|
||||||
SourceIP: "127.0.0.1",
|
SourceIP: "127.0.0.1",
|
||||||
|
@ -200,7 +246,7 @@ func TestGetAllConnections(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
|
@ -211,18 +257,28 @@ func TestGetAllConnections(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyConnectionsResponse)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyConnectionsResponse)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
for commandIndex, originalConnection := range commandInput.Connections {
|
for commandIndex, originalConnection := range commandInput.Connections {
|
||||||
remoteConnection := commandUnmarshalled.Connections[commandIndex]
|
remoteConnection := commandUnmarshalled.Connections[commandIndex]
|
||||||
|
|
||||||
|
@ -253,15 +309,16 @@ func TestGetAllConnections(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckClientParameters(t *testing.T) {
|
func TestCheckClientParametersMarshalSupport(t *testing.T) {
|
||||||
commandInput := &CheckClientParameters{
|
commandInput := &CheckClientParameters{
|
||||||
|
Type: "checkClientParameters",
|
||||||
SourceIP: "192.168.0.139",
|
SourceIP: "192.168.0.139",
|
||||||
SourcePort: 19132,
|
SourcePort: 19132,
|
||||||
DestPort: 19132,
|
DestPort: 19132,
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
|
@ -272,18 +329,28 @@ func TestCheckClientParameters(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("command type does not match up! (orig: %s, unmsh: %s)", commandType, commandInput.Type)
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*CheckClientParameters)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*CheckClientParameters)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
||||||
|
@ -305,12 +372,13 @@ func TestCheckClientParameters(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckServerParameters(t *testing.T) {
|
func TestCheckServerParametersMarshalSupport(t *testing.T) {
|
||||||
commandInput := &CheckServerParameters{
|
commandInput := &CheckServerParameters{
|
||||||
|
Type: "checkServerParameters",
|
||||||
Arguments: []byte("Hello from automated testing"),
|
Arguments: []byte("Hello from automated testing"),
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if logLevel == "debug" {
|
if logLevel == "debug" {
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
log.Printf("Generated array contents: %v", commandMarshalled)
|
||||||
|
@ -321,31 +389,42 @@ func TestCheckServerParameters(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*CheckServerParameters)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*CheckServerParameters)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if !bytes.Equal(commandInput.Arguments, commandUnmarshalled.Arguments) {
|
if !bytes.Equal(commandInput.Arguments, commandUnmarshalled.Arguments) {
|
||||||
log.Fatalf("Arguments are not equal (orig: '%s', unmsh: '%s')", string(commandInput.Arguments), string(commandUnmarshalled.Arguments))
|
log.Fatalf("Arguments are not equal (orig: '%s', unmsh: '%s')", string(commandInput.Arguments), string(commandUnmarshalled.Arguments))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckParametersResponse(t *testing.T) {
|
func TestCheckParametersResponseMarshalSupport(t *testing.T) {
|
||||||
commandInput := &CheckParametersResponse{
|
commandInput := &CheckParametersResponse{
|
||||||
|
Type: "checkParametersResponse",
|
||||||
InResponseTo: "checkClientParameters",
|
InResponseTo: "checkClientParameters",
|
||||||
IsValid: true,
|
IsValid: true,
|
||||||
Message: "Hello from automated testing",
|
Message: "Hello from automated testing",
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
|
@ -356,18 +435,28 @@ func TestCheckParametersResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("command type does not match up! (orig: %s, unmsh: %s)", commandType, commandInput.Type)
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*CheckParametersResponse)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*CheckParametersResponse)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if commandInput.InResponseTo != commandUnmarshalled.InResponseTo {
|
if commandInput.InResponseTo != commandUnmarshalled.InResponseTo {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
log.Printf("InResponseTo's are not equal (orig: %s, unmsh: %s)", commandInput.InResponseTo, commandUnmarshalled.InResponseTo)
|
log.Printf("InResponseTo's are not equal (orig: %s, unmsh: %s)", commandInput.InResponseTo, commandUnmarshalled.InResponseTo)
|
||||||
|
@ -384,9 +473,12 @@ func TestCheckParametersResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackendStatusRequest(t *testing.T) {
|
func TestBackendStatusRequestMarshalSupport(t *testing.T) {
|
||||||
commandInput := &BackendStatusRequest{}
|
commandInput := &BackendStatusRequest{
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
Type: "backendStatusRequest",
|
||||||
|
}
|
||||||
|
|
||||||
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if logLevel == "debug" {
|
if logLevel == "debug" {
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
log.Printf("Generated array contents: %v", commandMarshalled)
|
||||||
|
@ -397,27 +489,38 @@ func TestBackendStatusRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := commandUnmarshalledRaw.(*BackendStatusRequest)
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*BackendStatusRequest)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackendStatusResponse(t *testing.T) {
|
func TestBackendStatusResponseMarshalSupport(t *testing.T) {
|
||||||
commandInput := &BackendStatusResponse{
|
commandInput := &BackendStatusResponse{
|
||||||
|
Type: "backendStatusResponse",
|
||||||
IsRunning: true,
|
IsRunning: true,
|
||||||
StatusCode: StatusFailure,
|
StatusCode: StatusFailure,
|
||||||
Message: "Hello from automated testing",
|
Message: "Hello from automated testing",
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if logLevel == "debug" {
|
if logLevel == "debug" {
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
log.Printf("Generated array contents: %v", commandMarshalled)
|
||||||
|
@ -428,18 +531,28 @@ func TestBackendStatusResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*BackendStatusResponse)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*BackendStatusResponse)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if commandInput.IsRunning != commandUnmarshalled.IsRunning {
|
if commandInput.IsRunning != commandUnmarshalled.IsRunning {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
log.Printf("IsRunning's are not equal (orig: %t, unmsh: %t)", commandInput.IsRunning, commandUnmarshalled.IsRunning)
|
log.Printf("IsRunning's are not equal (orig: %t, unmsh: %t)", commandInput.IsRunning, commandUnmarshalled.IsRunning)
|
||||||
|
@ -456,15 +569,16 @@ func TestBackendStatusResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyStatusRequest(t *testing.T) {
|
func TestProxyStatusRequestMarshalSupport(t *testing.T) {
|
||||||
commandInput := &ProxyStatusRequest{
|
commandInput := &ProxyStatusRequest{
|
||||||
|
Type: "proxyStatusRequest",
|
||||||
SourceIP: "192.168.0.139",
|
SourceIP: "192.168.0.139",
|
||||||
SourcePort: 19132,
|
SourcePort: 19132,
|
||||||
DestPort: 19132,
|
DestPort: 19132,
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
|
@ -475,18 +589,28 @@ func TestProxyStatusRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyStatusRequest)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyStatusRequest)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
||||||
|
@ -508,8 +632,9 @@ func TestProxyStatusRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyStatusResponse(t *testing.T) {
|
func TestProxyStatusResponseMarshalSupport(t *testing.T) {
|
||||||
commandInput := &ProxyStatusResponse{
|
commandInput := &ProxyStatusResponse{
|
||||||
|
Type: "proxyStatusResponse",
|
||||||
SourceIP: "192.168.0.139",
|
SourceIP: "192.168.0.139",
|
||||||
SourcePort: 19132,
|
SourcePort: 19132,
|
||||||
DestPort: 19132,
|
DestPort: 19132,
|
||||||
|
@ -517,7 +642,7 @@ func TestProxyStatusResponse(t *testing.T) {
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
|
@ -528,18 +653,28 @@ func TestProxyStatusResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyStatusResponse)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyStatusResponse)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
||||||
|
@ -566,10 +701,12 @@ func TestProxyStatusResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyConnectionRequest(t *testing.T) {
|
func TestProxyConnectionRequestMarshalSupport(t *testing.T) {
|
||||||
commandInput := &ProxyInstanceRequest{}
|
commandInput := &ProxyInstanceRequest{
|
||||||
|
Type: "proxyInstanceRequest",
|
||||||
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if logLevel == "debug" {
|
if logLevel == "debug" {
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
log.Printf("Generated array contents: %v", commandMarshalled)
|
||||||
|
@ -580,21 +717,32 @@ func TestProxyConnectionRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := commandUnmarshalledRaw.(*ProxyInstanceRequest)
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyInstanceRequest)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyConnectionResponse(t *testing.T) {
|
func TestProxyConnectionResponseMarshalSupport(t *testing.T) {
|
||||||
commandInput := &ProxyInstanceResponse{
|
commandInput := &ProxyInstanceResponse{
|
||||||
|
Type: "proxyInstanceResponse",
|
||||||
Proxies: []*ProxyInstance{
|
Proxies: []*ProxyInstance{
|
||||||
{
|
{
|
||||||
SourceIP: "192.168.0.168",
|
SourceIP: "192.168.0.168",
|
||||||
|
@ -617,7 +765,7 @@ func TestProxyConnectionResponse(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
commandMarshalled, err := Marshal(commandInput.Type, commandInput)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
|
@ -628,18 +776,28 @@ func TestProxyConnectionResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
buf := bytes.NewBuffer(commandMarshalled)
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
commandType, commandUnmarshalledRaw, err := Unmarshal(buf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != commandInput.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Print("command type does not match up!")
|
||||||
|
}
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyInstanceResponse)
|
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyInstanceResponse)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("failed typecast")
|
t.Fatal("failed typecast")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandInput.Type != commandUnmarshalled.Type {
|
||||||
|
t.Fail()
|
||||||
|
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
|
||||||
|
}
|
||||||
|
|
||||||
for proxyIndex, originalProxy := range commandInput.Proxies {
|
for proxyIndex, originalProxy := range commandInput.Proxies {
|
||||||
remoteProxy := commandUnmarshalled.Proxies[proxyIndex]
|
remoteProxy := commandUnmarshalled.Proxies[proxyIndex]
|
||||||
|
|
||||||
|
|
|
@ -142,11 +142,11 @@ func unmarshalIndividualProxyStruct(conn io.Reader) (*ProxyInstance, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Unmarshal(conn io.Reader) (interface{}, error) {
|
func Unmarshal(conn io.Reader) (string, interface{}, error) {
|
||||||
commandType := make([]byte, 1)
|
commandType := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(commandType); err != nil {
|
if _, err := conn.Read(commandType); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read command")
|
return "", nil, fmt.Errorf("couldn't read command")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch commandType[0] {
|
switch commandType[0] {
|
||||||
|
@ -154,25 +154,28 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
argumentsLength := make([]byte, 2)
|
argumentsLength := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(argumentsLength); err != nil {
|
if _, err := conn.Read(argumentsLength); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read argument length")
|
return "", nil, fmt.Errorf("couldn't read argument length")
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments := make([]byte, binary.BigEndian.Uint16(argumentsLength))
|
arguments := make([]byte, binary.BigEndian.Uint16(argumentsLength))
|
||||||
|
|
||||||
if _, err := conn.Read(arguments); err != nil {
|
if _, err := conn.Read(arguments); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read arguments")
|
return "", nil, fmt.Errorf("couldn't read arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Start{
|
return "start", &Start{
|
||||||
|
Type: "start",
|
||||||
Arguments: arguments,
|
Arguments: arguments,
|
||||||
}, nil
|
}, nil
|
||||||
case StopID:
|
case StopID:
|
||||||
return &Stop{}, nil
|
return "stop", &Stop{
|
||||||
|
Type: "stop",
|
||||||
|
}, nil
|
||||||
case AddProxyID:
|
case AddProxyID:
|
||||||
ipVersion := make([]byte, 1)
|
ipVersion := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(ipVersion); err != nil {
|
if _, err := conn.Read(ipVersion); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read ip version")
|
return "", nil, fmt.Errorf("couldn't read ip version")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipSize uint8
|
var ipSize uint8
|
||||||
|
@ -182,44 +185,45 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
} else if ipVersion[0] == 6 {
|
} else if ipVersion[0] == 6 {
|
||||||
ipSize = IPv6Size
|
ipSize = IPv6Size
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid IP version recieved")
|
return "", nil, fmt.Errorf("invalid IP version recieved")
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := make(net.IP, ipSize)
|
ip := make(net.IP, ipSize)
|
||||||
|
|
||||||
if _, err := conn.Read(ip); err != nil {
|
if _, err := conn.Read(ip); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source IP")
|
return "", nil, fmt.Errorf("couldn't read source IP")
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcePort := make([]byte, 2)
|
sourcePort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(sourcePort); err != nil {
|
if _, err := conn.Read(sourcePort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source port")
|
return "", nil, fmt.Errorf("couldn't read source port")
|
||||||
}
|
}
|
||||||
|
|
||||||
destPort := make([]byte, 2)
|
destPort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(destPort); err != nil {
|
if _, err := conn.Read(destPort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read destination port")
|
return "", nil, fmt.Errorf("couldn't read destination port")
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolBytes := make([]byte, 1)
|
protocolBytes := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(protocolBytes); err != nil {
|
if _, err := conn.Read(protocolBytes); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read protocol")
|
return "", nil, fmt.Errorf("couldn't read protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
var protocol string
|
var protocol string
|
||||||
|
|
||||||
if protocolBytes[0] == TCP {
|
if protocolBytes[0] == TCP {
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
} else if protocolBytes[0] == UDP {
|
} else if protocolBytes[1] == UDP {
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return "", nil, fmt.Errorf("invalid protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AddProxy{
|
return "addProxy", &AddProxy{
|
||||||
|
Type: "addProxy",
|
||||||
SourceIP: ip.String(),
|
SourceIP: ip.String(),
|
||||||
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
||||||
DestPort: binary.BigEndian.Uint16(destPort),
|
DestPort: binary.BigEndian.Uint16(destPort),
|
||||||
|
@ -229,7 +233,7 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
ipVersion := make([]byte, 1)
|
ipVersion := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(ipVersion); err != nil {
|
if _, err := conn.Read(ipVersion); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read ip version")
|
return "", nil, fmt.Errorf("couldn't read ip version")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipSize uint8
|
var ipSize uint8
|
||||||
|
@ -239,44 +243,45 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
} else if ipVersion[0] == 6 {
|
} else if ipVersion[0] == 6 {
|
||||||
ipSize = IPv6Size
|
ipSize = IPv6Size
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid IP version recieved")
|
return "", nil, fmt.Errorf("invalid IP version recieved")
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := make(net.IP, ipSize)
|
ip := make(net.IP, ipSize)
|
||||||
|
|
||||||
if _, err := conn.Read(ip); err != nil {
|
if _, err := conn.Read(ip); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source IP")
|
return "", nil, fmt.Errorf("couldn't read source IP")
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcePort := make([]byte, 2)
|
sourcePort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(sourcePort); err != nil {
|
if _, err := conn.Read(sourcePort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source port")
|
return "", nil, fmt.Errorf("couldn't read source port")
|
||||||
}
|
}
|
||||||
|
|
||||||
destPort := make([]byte, 2)
|
destPort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(destPort); err != nil {
|
if _, err := conn.Read(destPort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read destination port")
|
return "", nil, fmt.Errorf("couldn't read destination port")
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolBytes := make([]byte, 1)
|
protocolBytes := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(protocolBytes); err != nil {
|
if _, err := conn.Read(protocolBytes); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read protocol")
|
return "", nil, fmt.Errorf("couldn't read protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
var protocol string
|
var protocol string
|
||||||
|
|
||||||
if protocolBytes[0] == TCP {
|
if protocolBytes[0] == TCP {
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
} else if protocolBytes[0] == UDP {
|
} else if protocolBytes[1] == UDP {
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return "", nil, fmt.Errorf("invalid protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &RemoveProxy{
|
return "removeProxy", &RemoveProxy{
|
||||||
|
Type: "removeProxy",
|
||||||
SourceIP: ip.String(),
|
SourceIP: ip.String(),
|
||||||
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
||||||
DestPort: binary.BigEndian.Uint16(destPort),
|
DestPort: binary.BigEndian.Uint16(destPort),
|
||||||
|
@ -296,13 +301,13 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
connections = append(connections, connection)
|
connections = append(connections, connection)
|
||||||
|
|
||||||
if _, err := conn.Read(delimiter); err != nil {
|
if _, err := conn.Read(delimiter); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read delimiter")
|
return "", nil, fmt.Errorf("couldn't read delimiter")
|
||||||
}
|
}
|
||||||
|
|
||||||
if delimiter[0] == '\r' {
|
if delimiter[0] == '\r' {
|
||||||
|
@ -316,14 +321,15 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProxyConnectionsResponse{
|
return "proxyConnectionsResponse", &ProxyConnectionsResponse{
|
||||||
|
Type: "proxyConnectionsResponse",
|
||||||
Connections: connections,
|
Connections: connections,
|
||||||
}, errorReturn
|
}, errorReturn
|
||||||
case CheckClientParametersID:
|
case CheckClientParametersID:
|
||||||
ipVersion := make([]byte, 1)
|
ipVersion := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(ipVersion); err != nil {
|
if _, err := conn.Read(ipVersion); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read ip version")
|
return "", nil, fmt.Errorf("couldn't read ip version")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipSize uint8
|
var ipSize uint8
|
||||||
|
@ -333,44 +339,45 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
} else if ipVersion[0] == 6 {
|
} else if ipVersion[0] == 6 {
|
||||||
ipSize = IPv6Size
|
ipSize = IPv6Size
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid IP version recieved")
|
return "", nil, fmt.Errorf("invalid IP version recieved")
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := make(net.IP, ipSize)
|
ip := make(net.IP, ipSize)
|
||||||
|
|
||||||
if _, err := conn.Read(ip); err != nil {
|
if _, err := conn.Read(ip); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source IP")
|
return "", nil, fmt.Errorf("couldn't read source IP")
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcePort := make([]byte, 2)
|
sourcePort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(sourcePort); err != nil {
|
if _, err := conn.Read(sourcePort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source port")
|
return "", nil, fmt.Errorf("couldn't read source port")
|
||||||
}
|
}
|
||||||
|
|
||||||
destPort := make([]byte, 2)
|
destPort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(destPort); err != nil {
|
if _, err := conn.Read(destPort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read destination port")
|
return "", nil, fmt.Errorf("couldn't read destination port")
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolBytes := make([]byte, 1)
|
protocolBytes := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(protocolBytes); err != nil {
|
if _, err := conn.Read(protocolBytes); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read protocol")
|
return "", nil, fmt.Errorf("couldn't read protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
var protocol string
|
var protocol string
|
||||||
|
|
||||||
if protocolBytes[0] == TCP {
|
if protocolBytes[0] == TCP {
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
} else if protocolBytes[0] == UDP {
|
} else if protocolBytes[1] == UDP {
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return "", nil, fmt.Errorf("invalid protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CheckClientParameters{
|
return "checkClientParameters", &CheckClientParameters{
|
||||||
|
Type: "checkClientParameters",
|
||||||
SourceIP: ip.String(),
|
SourceIP: ip.String(),
|
||||||
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
||||||
DestPort: binary.BigEndian.Uint16(destPort),
|
DestPort: binary.BigEndian.Uint16(destPort),
|
||||||
|
@ -380,23 +387,24 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
argumentsLength := make([]byte, 2)
|
argumentsLength := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(argumentsLength); err != nil {
|
if _, err := conn.Read(argumentsLength); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read argument length")
|
return "", nil, fmt.Errorf("couldn't read argument length")
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments := make([]byte, binary.BigEndian.Uint16(argumentsLength))
|
arguments := make([]byte, binary.BigEndian.Uint16(argumentsLength))
|
||||||
|
|
||||||
if _, err := conn.Read(arguments); err != nil {
|
if _, err := conn.Read(arguments); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read arguments")
|
return "", nil, fmt.Errorf("couldn't read arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CheckServerParameters{
|
return "checkServerParameters", &CheckServerParameters{
|
||||||
|
Type: "checkServerParameters",
|
||||||
Arguments: arguments,
|
Arguments: arguments,
|
||||||
}, nil
|
}, nil
|
||||||
case CheckParametersResponseID:
|
case CheckParametersResponseID:
|
||||||
checkMethodByte := make([]byte, 1)
|
checkMethodByte := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(checkMethodByte); err != nil {
|
if _, err := conn.Read(checkMethodByte); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read check method byte")
|
return "", nil, fmt.Errorf("couldn't read check method byte")
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkMethod string
|
var checkMethod string
|
||||||
|
@ -406,19 +414,19 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
} else if checkMethodByte[0] == CheckServerParametersID {
|
} else if checkMethodByte[0] == CheckServerParametersID {
|
||||||
checkMethod = "checkServerParameters"
|
checkMethod = "checkServerParameters"
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid check method recieved")
|
return "", nil, fmt.Errorf("invalid check method recieved")
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid := make([]byte, 1)
|
isValid := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(isValid); err != nil {
|
if _, err := conn.Read(isValid); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read isValid byte")
|
return "", nil, fmt.Errorf("couldn't read isValid byte")
|
||||||
}
|
}
|
||||||
|
|
||||||
messageLengthBytes := make([]byte, 2)
|
messageLengthBytes := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(messageLengthBytes); err != nil {
|
if _, err := conn.Read(messageLengthBytes); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read message length")
|
return "", nil, fmt.Errorf("couldn't read message length")
|
||||||
}
|
}
|
||||||
|
|
||||||
messageLength := binary.BigEndian.Uint16(messageLengthBytes)
|
messageLength := binary.BigEndian.Uint16(messageLengthBytes)
|
||||||
|
@ -428,13 +436,14 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
messageBytes := make([]byte, messageLength)
|
messageBytes := make([]byte, messageLength)
|
||||||
|
|
||||||
if _, err := conn.Read(messageBytes); err != nil {
|
if _, err := conn.Read(messageBytes); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read message")
|
return "", nil, fmt.Errorf("couldn't read message")
|
||||||
}
|
}
|
||||||
|
|
||||||
message = string(messageBytes)
|
message = string(messageBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CheckParametersResponse{
|
return "checkParametersResponse", &CheckParametersResponse{
|
||||||
|
Type: "checkParametersResponse",
|
||||||
InResponseTo: checkMethod,
|
InResponseTo: checkMethod,
|
||||||
IsValid: isValid[0] == 1,
|
IsValid: isValid[0] == 1,
|
||||||
Message: message,
|
Message: message,
|
||||||
|
@ -443,19 +452,19 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
isRunning := make([]byte, 1)
|
isRunning := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(isRunning); err != nil {
|
if _, err := conn.Read(isRunning); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read isRunning field")
|
return "", nil, fmt.Errorf("couldn't read isRunning field")
|
||||||
}
|
}
|
||||||
|
|
||||||
statusCode := make([]byte, 1)
|
statusCode := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(statusCode); err != nil {
|
if _, err := conn.Read(statusCode); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read status code field")
|
return "", nil, fmt.Errorf("couldn't read status code field")
|
||||||
}
|
}
|
||||||
|
|
||||||
messageLengthBytes := make([]byte, 2)
|
messageLengthBytes := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(messageLengthBytes); err != nil {
|
if _, err := conn.Read(messageLengthBytes); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read message length")
|
return "", nil, fmt.Errorf("couldn't read message length")
|
||||||
}
|
}
|
||||||
|
|
||||||
messageLength := binary.BigEndian.Uint16(messageLengthBytes)
|
messageLength := binary.BigEndian.Uint16(messageLengthBytes)
|
||||||
|
@ -465,24 +474,27 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
messageBytes := make([]byte, messageLength)
|
messageBytes := make([]byte, messageLength)
|
||||||
|
|
||||||
if _, err := conn.Read(messageBytes); err != nil {
|
if _, err := conn.Read(messageBytes); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read message")
|
return "", nil, fmt.Errorf("couldn't read message")
|
||||||
}
|
}
|
||||||
|
|
||||||
message = string(messageBytes)
|
message = string(messageBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BackendStatusResponse{
|
return "backendStatusResponse", &BackendStatusResponse{
|
||||||
|
Type: "backendStatusResponse",
|
||||||
IsRunning: isRunning[0] == 1,
|
IsRunning: isRunning[0] == 1,
|
||||||
StatusCode: int(statusCode[0]),
|
StatusCode: int(statusCode[0]),
|
||||||
Message: message,
|
Message: message,
|
||||||
}, nil
|
}, nil
|
||||||
case BackendStatusRequestID:
|
case BackendStatusRequestID:
|
||||||
return &BackendStatusRequest{}, nil
|
return "backendStatusRequest", &BackendStatusRequest{
|
||||||
|
Type: "backendStatusRequest",
|
||||||
|
}, nil
|
||||||
case ProxyStatusRequestID:
|
case ProxyStatusRequestID:
|
||||||
ipVersion := make([]byte, 1)
|
ipVersion := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(ipVersion); err != nil {
|
if _, err := conn.Read(ipVersion); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read ip version")
|
return "", nil, fmt.Errorf("couldn't read ip version")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipSize uint8
|
var ipSize uint8
|
||||||
|
@ -492,44 +504,45 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
} else if ipVersion[0] == 6 {
|
} else if ipVersion[0] == 6 {
|
||||||
ipSize = IPv6Size
|
ipSize = IPv6Size
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid IP version recieved")
|
return "", nil, fmt.Errorf("invalid IP version recieved")
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := make(net.IP, ipSize)
|
ip := make(net.IP, ipSize)
|
||||||
|
|
||||||
if _, err := conn.Read(ip); err != nil {
|
if _, err := conn.Read(ip); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source IP")
|
return "", nil, fmt.Errorf("couldn't read source IP")
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcePort := make([]byte, 2)
|
sourcePort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(sourcePort); err != nil {
|
if _, err := conn.Read(sourcePort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source port")
|
return "", nil, fmt.Errorf("couldn't read source port")
|
||||||
}
|
}
|
||||||
|
|
||||||
destPort := make([]byte, 2)
|
destPort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(destPort); err != nil {
|
if _, err := conn.Read(destPort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read destination port")
|
return "", nil, fmt.Errorf("couldn't read destination port")
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolBytes := make([]byte, 1)
|
protocolBytes := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(protocolBytes); err != nil {
|
if _, err := conn.Read(protocolBytes); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read protocol")
|
return "", nil, fmt.Errorf("couldn't read protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
var protocol string
|
var protocol string
|
||||||
|
|
||||||
if protocolBytes[0] == TCP {
|
if protocolBytes[0] == TCP {
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
} else if protocolBytes[0] == UDP {
|
} else if protocolBytes[1] == UDP {
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return "", nil, fmt.Errorf("invalid protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProxyStatusRequest{
|
return "proxyStatusRequest", &ProxyStatusRequest{
|
||||||
|
Type: "proxyStatusRequest",
|
||||||
SourceIP: ip.String(),
|
SourceIP: ip.String(),
|
||||||
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
||||||
DestPort: binary.BigEndian.Uint16(destPort),
|
DestPort: binary.BigEndian.Uint16(destPort),
|
||||||
|
@ -539,7 +552,7 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
ipVersion := make([]byte, 1)
|
ipVersion := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(ipVersion); err != nil {
|
if _, err := conn.Read(ipVersion); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read ip version")
|
return "", nil, fmt.Errorf("couldn't read ip version")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipSize uint8
|
var ipSize uint8
|
||||||
|
@ -549,50 +562,51 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
} else if ipVersion[0] == 6 {
|
} else if ipVersion[0] == 6 {
|
||||||
ipSize = IPv6Size
|
ipSize = IPv6Size
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid IP version recieved")
|
return "", nil, fmt.Errorf("invalid IP version recieved")
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := make(net.IP, ipSize)
|
ip := make(net.IP, ipSize)
|
||||||
|
|
||||||
if _, err := conn.Read(ip); err != nil {
|
if _, err := conn.Read(ip); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source IP")
|
return "", nil, fmt.Errorf("couldn't read source IP")
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcePort := make([]byte, 2)
|
sourcePort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(sourcePort); err != nil {
|
if _, err := conn.Read(sourcePort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read source port")
|
return "", nil, fmt.Errorf("couldn't read source port")
|
||||||
}
|
}
|
||||||
|
|
||||||
destPort := make([]byte, 2)
|
destPort := make([]byte, 2)
|
||||||
|
|
||||||
if _, err := conn.Read(destPort); err != nil {
|
if _, err := conn.Read(destPort); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read destination port")
|
return "", nil, fmt.Errorf("couldn't read destination port")
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolBytes := make([]byte, 1)
|
protocolBytes := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(protocolBytes); err != nil {
|
if _, err := conn.Read(protocolBytes); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read protocol")
|
return "", nil, fmt.Errorf("couldn't read protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
var protocol string
|
var protocol string
|
||||||
|
|
||||||
if protocolBytes[0] == TCP {
|
if protocolBytes[0] == TCP {
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
} else if protocolBytes[0] == UDP {
|
} else if protocolBytes[1] == UDP {
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid protocol")
|
return "", nil, fmt.Errorf("invalid protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive := make([]byte, 1)
|
isActive := make([]byte, 1)
|
||||||
|
|
||||||
if _, err := conn.Read(isActive); err != nil {
|
if _, err := conn.Read(isActive); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read isActive field")
|
return "", nil, fmt.Errorf("couldn't read isActive field")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProxyStatusResponse{
|
return "proxyStatusResponse", &ProxyStatusResponse{
|
||||||
|
Type: "proxyStatusResponse",
|
||||||
SourceIP: ip.String(),
|
SourceIP: ip.String(),
|
||||||
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
SourcePort: binary.BigEndian.Uint16(sourcePort),
|
||||||
DestPort: binary.BigEndian.Uint16(destPort),
|
DestPort: binary.BigEndian.Uint16(destPort),
|
||||||
|
@ -600,7 +614,9 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
IsActive: isActive[0] == 1,
|
IsActive: isActive[0] == 1,
|
||||||
}, nil
|
}, nil
|
||||||
case ProxyInstanceRequestID:
|
case ProxyInstanceRequestID:
|
||||||
return &ProxyInstanceRequest{}, nil
|
return "proxyInstanceRequest", &ProxyInstanceRequest{
|
||||||
|
Type: "proxyInstanceRequest",
|
||||||
|
}, nil
|
||||||
case ProxyInstanceResponseID:
|
case ProxyInstanceResponseID:
|
||||||
proxies := []*ProxyInstance{}
|
proxies := []*ProxyInstance{}
|
||||||
delimiter := make([]byte, 1)
|
delimiter := make([]byte, 1)
|
||||||
|
@ -615,13 +631,13 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies = append(proxies, proxy)
|
proxies = append(proxies, proxy)
|
||||||
|
|
||||||
if _, err := conn.Read(delimiter); err != nil {
|
if _, err := conn.Read(delimiter); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read delimiter")
|
return "", nil, fmt.Errorf("couldn't read delimiter")
|
||||||
}
|
}
|
||||||
|
|
||||||
if delimiter[0] == '\r' {
|
if delimiter[0] == '\r' {
|
||||||
|
@ -635,12 +651,15 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProxyInstanceResponse{
|
return "proxyInstanceResponse", &ProxyInstanceResponse{
|
||||||
|
Type: "proxyInstanceResponse",
|
||||||
Proxies: proxies,
|
Proxies: proxies,
|
||||||
}, errorReturn
|
}, errorReturn
|
||||||
case ProxyConnectionsRequestID:
|
case ProxyConnectionsRequestID:
|
||||||
return &ProxyConnectionsRequest{}, nil
|
return "proxyConnectionsRequest", &ProxyConnectionsRequest{
|
||||||
|
Type: "proxyConnectionsRequest",
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("couldn't match command ID")
|
return "", nil, fmt.Errorf("couldn't match command ID")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/backendutil"
|
"git.terah.dev/imterah/hermes/backendutil"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,10 +19,6 @@ func (backend *DummyBackend) StopBackend() (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backend *DummyBackend) GetBackendStatus() (bool, error) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *DummyBackend) StartProxy(command *commonbackend.AddProxy) (bool, error) {
|
func (backend *DummyBackend) StartProxy(command *commonbackend.AddProxy) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/backendlauncher"
|
"git.terah.dev/imterah/hermes/backendlauncher"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
@ -21,8 +21,11 @@ type ProxyInstance struct {
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WriteLogger struct{}
|
type WriteLogger struct {
|
||||||
|
UseError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: deprecate UseError switching
|
||||||
func (writer WriteLogger) Write(p []byte) (n int, err error) {
|
func (writer WriteLogger) Write(p []byte) (n int, err error) {
|
||||||
logSplit := strings.Split(string(p), "\n")
|
logSplit := strings.Split(string(p), "\n")
|
||||||
|
|
||||||
|
@ -31,8 +34,12 @@ func (writer WriteLogger) Write(p []byte) (n int, err error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if writer.UseError {
|
||||||
|
log.Errorf("application: %s", line)
|
||||||
|
} else {
|
||||||
log.Infof("application: %s", line)
|
log.Infof("application: %s", line)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return len(p), err
|
return len(p), err
|
||||||
}
|
}
|
||||||
|
@ -112,10 +119,11 @@ func entrypoint(cCtx *cli.Context) error {
|
||||||
defer sock.Close()
|
defer sock.Close()
|
||||||
|
|
||||||
startCommand := &commonbackend.Start{
|
startCommand := &commonbackend.Start{
|
||||||
|
Type: "start",
|
||||||
Arguments: backendParameters,
|
Arguments: backendParameters,
|
||||||
}
|
}
|
||||||
|
|
||||||
startMarshalledCommand, err := commonbackend.Marshal(startCommand)
|
startMarshalledCommand, err := commonbackend.Marshal("start", startCommand)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to generate start command: %s", err.Error())
|
log.Errorf("failed to generate start command: %s", err.Error())
|
||||||
|
@ -127,13 +135,18 @@ func entrypoint(cCtx *cli.Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
commandRaw, err := commonbackend.Unmarshal(sock)
|
commandType, commandRaw, err := commonbackend.Unmarshal(sock)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to read from/unmarshal from socket: %s", err.Error())
|
log.Errorf("failed to read from/unmarshal from socket: %s", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != "backendStatusResponse" {
|
||||||
|
log.Errorf("recieved commandType '%s', expecting 'backendStatusResponse'", commandType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
command, ok := commandRaw.(*commonbackend.BackendStatusResponse)
|
command, ok := commandRaw.(*commonbackend.BackendStatusResponse)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -162,13 +175,14 @@ func entrypoint(cCtx *cli.Context) error {
|
||||||
log.Infof("initializing proxy %s:%d -> remote:%d", proxy.SourceIP, proxy.SourcePort, proxy.DestPort)
|
log.Infof("initializing proxy %s:%d -> remote:%d", proxy.SourceIP, proxy.SourcePort, proxy.DestPort)
|
||||||
|
|
||||||
proxyAddCommand := &commonbackend.AddProxy{
|
proxyAddCommand := &commonbackend.AddProxy{
|
||||||
|
Type: "addProxy",
|
||||||
SourceIP: proxy.SourceIP,
|
SourceIP: proxy.SourceIP,
|
||||||
SourcePort: proxy.SourcePort,
|
SourcePort: proxy.SourcePort,
|
||||||
DestPort: proxy.DestPort,
|
DestPort: proxy.DestPort,
|
||||||
Protocol: proxy.Protocol,
|
Protocol: proxy.Protocol,
|
||||||
}
|
}
|
||||||
|
|
||||||
marshalledProxyCommand, err := commonbackend.Marshal(proxyAddCommand)
|
marshalledProxyCommand, err := commonbackend.Marshal("addProxy", proxyAddCommand)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to generate start command: %s", err.Error())
|
log.Errorf("failed to generate start command: %s", err.Error())
|
||||||
|
@ -182,7 +196,7 @@ func entrypoint(cCtx *cli.Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
commandRaw, err := commonbackend.Unmarshal(sock)
|
commandType, commandRaw, err := commonbackend.Unmarshal(sock)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to read from/unmarshal from socket: %s", err.Error())
|
log.Errorf("failed to read from/unmarshal from socket: %s", err.Error())
|
||||||
|
@ -190,6 +204,12 @@ func entrypoint(cCtx *cli.Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commandType != "proxyStatusResponse" {
|
||||||
|
log.Errorf("recieved commandType '%s', expecting 'proxyStatusResponse'", commandType)
|
||||||
|
hasAnyFailed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
command, ok := commandRaw.(*commonbackend.ProxyStatusResponse)
|
command, ok := commandRaw.(*commonbackend.ProxyStatusResponse)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -222,8 +242,13 @@ func entrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Debug("entering execution loop (in main goroutine)...")
|
log.Debug("entering execution loop (in main goroutine)...")
|
||||||
|
|
||||||
stdout := WriteLogger{}
|
stdout := WriteLogger{
|
||||||
stderr := WriteLogger{}
|
UseError: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr := WriteLogger{
|
||||||
|
UseError: true,
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
log.Info("starting process...")
|
log.Info("starting process...")
|
||||||
|
|
|
@ -2,36 +2,24 @@ module git.terah.dev/imterah/hermes
|
||||||
|
|
||||||
go 1.23.3
|
go 1.23.3
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/charmbracelet/log v0.4.0
|
|
||||||
github.com/gin-gonic/gin v1.10.0
|
|
||||||
github.com/go-playground/validator/v10 v10.23.0
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
|
||||||
github.com/pkg/sftp v1.13.7
|
|
||||||
github.com/urfave/cli/v2 v2.27.5
|
|
||||||
golang.org/x/crypto v0.31.0
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
|
||||||
golang.org/x/term v0.28.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
gorm.io/driver/postgres v1.5.11
|
|
||||||
gorm.io/driver/sqlite v1.5.7
|
|
||||||
gorm.io/gorm v1.25.12
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/bytedance/sonic v1.12.6 // indirect
|
github.com/bytedance/sonic v1.12.6 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||||
github.com/charmbracelet/lipgloss v0.10.0 // indirect
|
github.com/charmbracelet/lipgloss v0.10.0 // indirect
|
||||||
|
github.com/charmbracelet/log v0.4.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.4 // indirect
|
github.com/goccy/go-json v0.10.4 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.7.2 // indirect
|
github.com/jackc/pgx/v5 v5.7.2 // indirect
|
||||||
|
@ -40,8 +28,6 @@ require (
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
@ -53,15 +39,21 @@ require (
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/urfave/cli/v2 v2.27.5 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
golang.org/x/arch v0.12.0 // indirect
|
golang.org/x/arch v0.12.0 // indirect
|
||||||
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.33.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.0 // indirect
|
google.golang.org/protobuf v1.36.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
gorm.io/driver/postgres v1.5.11 // indirect
|
||||||
|
gorm.io/driver/sqlite v1.5.7 // indirect
|
||||||
|
gorm.io/gorm v1.25.12 // indirect
|
||||||
)
|
)
|
|
@ -15,9 +15,7 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
||||||
|
@ -27,8 +25,6 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
@ -39,8 +35,6 @@ github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
@ -60,16 +54,12 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||||
|
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
@ -88,16 +78,11 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo
|
||||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
|
|
||||||
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
@ -108,7 +93,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
@ -118,66 +102,30 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
|
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
|
||||||
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
|
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
|
||||||
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -188,3 +136,4 @@ gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDa
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@ -1,90 +0,0 @@
|
||||||
package datacommands
|
|
||||||
|
|
||||||
// DO NOT USE
|
|
||||||
type ProxyStatusRequest struct {
|
|
||||||
ProxyID uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyStatusResponse struct {
|
|
||||||
ProxyID uint16
|
|
||||||
IsActive bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoveProxy struct {
|
|
||||||
ProxyID uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyInstanceResponse struct {
|
|
||||||
Proxies []uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyConnectionsRequest struct {
|
|
||||||
ProxyID uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyConnectionsResponse struct {
|
|
||||||
Connections []uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type TCPConnectionOpened struct {
|
|
||||||
ProxyID uint16
|
|
||||||
ConnectionID uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type TCPConnectionClosed struct {
|
|
||||||
ProxyID uint16
|
|
||||||
ConnectionID uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type TCPProxyData struct {
|
|
||||||
ProxyID uint16
|
|
||||||
ConnectionID uint16
|
|
||||||
DataLength uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type UDPProxyData struct {
|
|
||||||
ProxyID uint16
|
|
||||||
ClientIP string
|
|
||||||
ClientPort uint16
|
|
||||||
DataLength uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyInformationRequest struct {
|
|
||||||
ProxyID uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyInformationResponse struct {
|
|
||||||
Exists bool
|
|
||||||
SourceIP string
|
|
||||||
SourcePort uint16
|
|
||||||
DestPort uint16
|
|
||||||
Protocol string // Will be either 'tcp' or 'udp'
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyConnectionInformationRequest struct {
|
|
||||||
ProxyID uint16
|
|
||||||
ConnectionID uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyConnectionInformationResponse struct {
|
|
||||||
Exists bool
|
|
||||||
ClientIP string
|
|
||||||
ClientPort uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
ProxyStatusRequestID = iota + 100
|
|
||||||
ProxyStatusResponseID
|
|
||||||
RemoveProxyID
|
|
||||||
ProxyInstanceResponseID
|
|
||||||
ProxyConnectionsRequestID
|
|
||||||
ProxyConnectionsResponseID
|
|
||||||
TCPConnectionOpenedID
|
|
||||||
TCPConnectionClosedID
|
|
||||||
TCPProxyDataID
|
|
||||||
UDPProxyDataID
|
|
||||||
ProxyInformationRequestID
|
|
||||||
ProxyInformationResponseID
|
|
||||||
ProxyConnectionInformationRequestID
|
|
||||||
ProxyConnectionInformationResponseID
|
|
||||||
)
|
|
|
@ -1,323 +0,0 @@
|
||||||
package datacommands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Example size and protocol constants — adjust as needed.
|
|
||||||
const (
|
|
||||||
IPv4Size = 4
|
|
||||||
IPv6Size = 16
|
|
||||||
|
|
||||||
TCP = 1
|
|
||||||
UDP = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// Marshal takes a command (pointer to one of our structs) and converts it to a byte slice.
|
|
||||||
func Marshal(command interface{}) ([]byte, error) {
|
|
||||||
switch cmd := command.(type) {
|
|
||||||
// ProxyStatusRequest: 1 byte for the command ID + 2 bytes for the ProxyID.
|
|
||||||
case *ProxyStatusRequest:
|
|
||||||
buf := make([]byte, 1+2)
|
|
||||||
|
|
||||||
buf[0] = ProxyStatusRequestID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// ProxyStatusResponse: 1 byte for the command ID, 2 bytes for ProxyID, and 1 byte for IsActive.
|
|
||||||
case *ProxyStatusResponse:
|
|
||||||
buf := make([]byte, 1+2+1)
|
|
||||||
|
|
||||||
buf[0] = ProxyStatusResponseID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
|
|
||||||
|
|
||||||
if cmd.IsActive {
|
|
||||||
buf[3] = 1
|
|
||||||
} else {
|
|
||||||
buf[3] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// RemoveProxy: 1 byte for the command ID + 2 bytes for the ProxyID.
|
|
||||||
case *RemoveProxy:
|
|
||||||
buf := make([]byte, 1+2)
|
|
||||||
|
|
||||||
buf[0] = RemoveProxyID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// ProxyConnectionsRequest: 1 byte for the command ID + 2 bytes for the ProxyID.
|
|
||||||
case *ProxyConnectionsRequest:
|
|
||||||
buf := make([]byte, 1+2)
|
|
||||||
|
|
||||||
buf[0] = ProxyConnectionsRequestID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// ProxyConnectionsResponse: 1 byte for the command ID + 2 bytes length of the Connections + 2 bytes for each
|
|
||||||
// number in the Connection array.
|
|
||||||
case *ProxyConnectionsResponse:
|
|
||||||
buf := make([]byte, 1+((len(cmd.Connections)+1)*2))
|
|
||||||
|
|
||||||
buf[0] = ProxyConnectionsResponseID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], uint16(len(cmd.Connections)))
|
|
||||||
|
|
||||||
for connectionIndex, connection := range cmd.Connections {
|
|
||||||
binary.BigEndian.PutUint16(buf[3+(connectionIndex*2):], connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// ProxyConnectionsResponse: 1 byte for the command ID + 2 bytes length of the Proxies + 2 bytes for each
|
|
||||||
// number in the Proxies array.
|
|
||||||
case *ProxyInstanceResponse:
|
|
||||||
buf := make([]byte, 1+((len(cmd.Proxies)+1)*2))
|
|
||||||
|
|
||||||
buf[0] = ProxyInstanceResponseID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], uint16(len(cmd.Proxies)))
|
|
||||||
|
|
||||||
for connectionIndex, connection := range cmd.Proxies {
|
|
||||||
binary.BigEndian.PutUint16(buf[3+(connectionIndex*2):], connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// TCPConnectionOpened: 1 byte for the command ID + 2 bytes ProxyID + 2 bytes ConnectionID.
|
|
||||||
case *TCPConnectionOpened:
|
|
||||||
buf := make([]byte, 1+2+2)
|
|
||||||
|
|
||||||
buf[0] = TCPConnectionOpenedID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
|
|
||||||
binary.BigEndian.PutUint16(buf[3:], cmd.ConnectionID)
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// TCPConnectionClosed: 1 byte for the command ID + 2 bytes ProxyID + 2 bytes ConnectionID.
|
|
||||||
case *TCPConnectionClosed:
|
|
||||||
buf := make([]byte, 1+2+2)
|
|
||||||
|
|
||||||
buf[0] = TCPConnectionClosedID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
|
|
||||||
binary.BigEndian.PutUint16(buf[3:], cmd.ConnectionID)
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// TCPProxyData: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID + 2 bytes DataLength.
|
|
||||||
case *TCPProxyData:
|
|
||||||
buf := make([]byte, 1+2+2+2)
|
|
||||||
|
|
||||||
buf[0] = TCPProxyDataID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
|
|
||||||
binary.BigEndian.PutUint16(buf[3:], cmd.ConnectionID)
|
|
||||||
binary.BigEndian.PutUint16(buf[5:], cmd.DataLength)
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// UDPProxyData:
|
|
||||||
// Format: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID +
|
|
||||||
// 1 byte IP version + IP bytes + 2 bytes ClientPort + 2 bytes DataLength.
|
|
||||||
case *UDPProxyData:
|
|
||||||
ip := net.ParseIP(cmd.ClientIP)
|
|
||||||
if ip == nil {
|
|
||||||
return nil, fmt.Errorf("invalid client IP: %v", cmd.ClientIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipVer uint8
|
|
||||||
var ipBytes []byte
|
|
||||||
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
ipBytes = ip4
|
|
||||||
ipVer = 4
|
|
||||||
} else if ip16 := ip.To16(); ip16 != nil {
|
|
||||||
ipBytes = ip16
|
|
||||||
ipVer = 6
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("unable to detect IP version for: %v", cmd.ClientIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalSize := 1 + // id
|
|
||||||
2 + // ProxyID
|
|
||||||
1 + // IP version
|
|
||||||
len(ipBytes) + // client IP bytes
|
|
||||||
2 + // ClientPort
|
|
||||||
2 // DataLength
|
|
||||||
|
|
||||||
buf := make([]byte, totalSize)
|
|
||||||
offset := 0
|
|
||||||
buf[offset] = UDPProxyDataID
|
|
||||||
offset++
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(buf[offset:], cmd.ProxyID)
|
|
||||||
offset += 2
|
|
||||||
|
|
||||||
buf[offset] = ipVer
|
|
||||||
offset++
|
|
||||||
|
|
||||||
copy(buf[offset:], ipBytes)
|
|
||||||
offset += len(ipBytes)
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(buf[offset:], cmd.ClientPort)
|
|
||||||
offset += 2
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(buf[offset:], cmd.DataLength)
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// ProxyInformationRequest: 1 byte ID + 2 bytes ProxyID.
|
|
||||||
case *ProxyInformationRequest:
|
|
||||||
buf := make([]byte, 1+2)
|
|
||||||
buf[0] = ProxyInformationRequestID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// ProxyInformationResponse:
|
|
||||||
// Format: 1 byte ID + 1 byte Exists + (if exists:)
|
|
||||||
// 1 byte IP version + IP bytes + 2 bytes SourcePort + 2 bytes DestPort + 1 byte Protocol.
|
|
||||||
// (For simplicity, this marshaller always writes the IP and port info even if !Exists.)
|
|
||||||
case *ProxyInformationResponse:
|
|
||||||
if !cmd.Exists {
|
|
||||||
buf := make([]byte, 1+1)
|
|
||||||
buf[0] = ProxyInformationResponseID
|
|
||||||
buf[1] = 0 /* false */
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(cmd.SourceIP)
|
|
||||||
|
|
||||||
if ip == nil {
|
|
||||||
return nil, fmt.Errorf("invalid source IP: %v", cmd.SourceIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipVer uint8
|
|
||||||
var ipBytes []byte
|
|
||||||
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
ipBytes = ip4
|
|
||||||
ipVer = 4
|
|
||||||
} else if ip16 := ip.To16(); ip16 != nil {
|
|
||||||
ipBytes = ip16
|
|
||||||
ipVer = 6
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("unable to detect IP version for: %v", cmd.SourceIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalSize := 1 + // id
|
|
||||||
1 + // Exists flag
|
|
||||||
1 + // IP version
|
|
||||||
len(ipBytes) +
|
|
||||||
2 + // SourcePort
|
|
||||||
2 + // DestPort
|
|
||||||
1 // Protocol
|
|
||||||
|
|
||||||
buf := make([]byte, totalSize)
|
|
||||||
|
|
||||||
offset := 0
|
|
||||||
buf[offset] = ProxyInformationResponseID
|
|
||||||
offset++
|
|
||||||
|
|
||||||
// We already handle this above
|
|
||||||
buf[offset] = 1 /* true */
|
|
||||||
offset++
|
|
||||||
|
|
||||||
buf[offset] = ipVer
|
|
||||||
offset++
|
|
||||||
|
|
||||||
copy(buf[offset:], ipBytes)
|
|
||||||
offset += len(ipBytes)
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(buf[offset:], cmd.SourcePort)
|
|
||||||
offset += 2
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(buf[offset:], cmd.DestPort)
|
|
||||||
offset += 2
|
|
||||||
|
|
||||||
// Encode protocol as 1 byte.
|
|
||||||
switch cmd.Protocol {
|
|
||||||
case "tcp":
|
|
||||||
buf[offset] = TCP
|
|
||||||
case "udp":
|
|
||||||
buf[offset] = UDP
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid protocol: %v", cmd.Protocol)
|
|
||||||
}
|
|
||||||
|
|
||||||
// offset++ (not needed since we are at the end)
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// ProxyConnectionInformationRequest: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID.
|
|
||||||
case *ProxyConnectionInformationRequest:
|
|
||||||
buf := make([]byte, 1+2+2)
|
|
||||||
|
|
||||||
buf[0] = ProxyConnectionInformationRequestID
|
|
||||||
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
|
|
||||||
binary.BigEndian.PutUint16(buf[3:], cmd.ConnectionID)
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
// ProxyConnectionInformationResponse:
|
|
||||||
// Format: 1 byte ID + 1 byte Exists + (if exists:)
|
|
||||||
// 1 byte IP version + IP bytes + 2 bytes ClientPort.
|
|
||||||
// This marshaller only writes the rest of the data if Exists.
|
|
||||||
case *ProxyConnectionInformationResponse:
|
|
||||||
if !cmd.Exists {
|
|
||||||
buf := make([]byte, 1+1)
|
|
||||||
buf[0] = ProxyConnectionInformationResponseID
|
|
||||||
buf[1] = 0 /* false */
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(cmd.ClientIP)
|
|
||||||
|
|
||||||
if ip == nil {
|
|
||||||
return nil, fmt.Errorf("invalid client IP: %v", cmd.ClientIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipVer uint8
|
|
||||||
var ipBytes []byte
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
ipBytes = ip4
|
|
||||||
ipVer = 4
|
|
||||||
} else if ip16 := ip.To16(); ip16 != nil {
|
|
||||||
ipBytes = ip16
|
|
||||||
ipVer = 6
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("unable to detect IP version for: %v", cmd.ClientIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalSize := 1 + // id
|
|
||||||
1 + // Exists flag
|
|
||||||
1 + // IP version
|
|
||||||
len(ipBytes) +
|
|
||||||
2 // ClientPort
|
|
||||||
|
|
||||||
buf := make([]byte, totalSize)
|
|
||||||
offset := 0
|
|
||||||
buf[offset] = ProxyConnectionInformationResponseID
|
|
||||||
offset++
|
|
||||||
|
|
||||||
// We already handle this above
|
|
||||||
buf[offset] = 1 /* true */
|
|
||||||
offset++
|
|
||||||
|
|
||||||
buf[offset] = ipVer
|
|
||||||
offset++
|
|
||||||
|
|
||||||
copy(buf[offset:], ipBytes)
|
|
||||||
offset += len(ipBytes)
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(buf[offset:], cmd.ClientPort)
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported command type")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,652 +0,0 @@
|
||||||
package datacommands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var logLevel = os.Getenv("HERMES_LOG_LEVEL")
|
|
||||||
|
|
||||||
func TestProxyStatusRequest(t *testing.T) {
|
|
||||||
commandInput := &ProxyStatusRequest{
|
|
||||||
ProxyID: 19132,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyStatusRequest)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyStatusResponse(t *testing.T) {
|
|
||||||
commandInput := &ProxyStatusResponse{
|
|
||||||
ProxyID: 19132,
|
|
||||||
IsActive: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyStatusResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.IsActive != commandUnmarshalled.IsActive {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("IsActive's are not equal (orig: '%t', unmsh: '%t')", commandInput.IsActive, commandUnmarshalled.IsActive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveProxy(t *testing.T) {
|
|
||||||
commandInput := &RemoveProxy{
|
|
||||||
ProxyID: 19132,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*RemoveProxy)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyConnectionsRequest(t *testing.T) {
|
|
||||||
commandInput := &ProxyConnectionsRequest{
|
|
||||||
ProxyID: 19132,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyConnectionsRequest)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyConnectionsResponse(t *testing.T) {
|
|
||||||
commandInput := &ProxyConnectionsResponse{
|
|
||||||
Connections: []uint16{12831, 9455, 64219, 12, 32},
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyConnectionsResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
for connectionIndex, originalConnection := range commandInput.Connections {
|
|
||||||
remoteConnection := commandUnmarshalled.Connections[connectionIndex]
|
|
||||||
|
|
||||||
if originalConnection != remoteConnection {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("(in #%d) SourceIP's are not equal (orig: %d, unmsh: %d)", connectionIndex, originalConnection, connectionIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyInstanceResponse(t *testing.T) {
|
|
||||||
commandInput := &ProxyInstanceResponse{
|
|
||||||
Proxies: []uint16{12831, 9455, 64219, 12, 32},
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyInstanceResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
for proxyIndex, originalProxy := range commandInput.Proxies {
|
|
||||||
remoteProxy := commandUnmarshalled.Proxies[proxyIndex]
|
|
||||||
|
|
||||||
if originalProxy != remoteProxy {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("(in #%d) Proxy IDs are not equal (orig: %d, unmsh: %d)", proxyIndex, originalProxy, remoteProxy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTCPConnectionOpened(t *testing.T) {
|
|
||||||
commandInput := &TCPConnectionOpened{
|
|
||||||
ProxyID: 19132,
|
|
||||||
ConnectionID: 25565,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*TCPConnectionOpened)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ConnectionID != commandUnmarshalled.ConnectionID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ConnectionID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ConnectionID, commandUnmarshalled.ConnectionID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTCPConnectionClosed(t *testing.T) {
|
|
||||||
commandInput := &TCPConnectionClosed{
|
|
||||||
ProxyID: 19132,
|
|
||||||
ConnectionID: 25565,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*TCPConnectionClosed)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ConnectionID != commandUnmarshalled.ConnectionID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ConnectionID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ConnectionID, commandUnmarshalled.ConnectionID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTCPProxyData(t *testing.T) {
|
|
||||||
commandInput := &TCPProxyData{
|
|
||||||
ProxyID: 19132,
|
|
||||||
ConnectionID: 25565,
|
|
||||||
DataLength: 1234,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*TCPProxyData)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ConnectionID != commandUnmarshalled.ConnectionID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ConnectionID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ConnectionID, commandUnmarshalled.ConnectionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.DataLength != commandUnmarshalled.DataLength {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("DataLength's are not equal (orig: '%d', unmsh: '%d')", commandInput.DataLength, commandUnmarshalled.DataLength)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUDPProxyData(t *testing.T) {
|
|
||||||
commandInput := &UDPProxyData{
|
|
||||||
ProxyID: 19132,
|
|
||||||
ClientIP: "68.51.23.54",
|
|
||||||
ClientPort: 28173,
|
|
||||||
DataLength: 1234,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*UDPProxyData)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ClientIP != commandUnmarshalled.ClientIP {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ClientIP's are not equal (orig: '%s', unmsh: '%s')", commandInput.ClientIP, commandUnmarshalled.ClientIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ClientPort != commandUnmarshalled.ClientPort {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ClientPort's are not equal (orig: '%d', unmsh: '%d')", commandInput.ClientPort, commandUnmarshalled.ClientPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.DataLength != commandUnmarshalled.DataLength {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("DataLength's are not equal (orig: '%d', unmsh: '%d')", commandInput.DataLength, commandUnmarshalled.DataLength)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyInformationRequest(t *testing.T) {
|
|
||||||
commandInput := &ProxyInformationRequest{
|
|
||||||
ProxyID: 19132,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyInformationRequest)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyInformationResponseExists(t *testing.T) {
|
|
||||||
commandInput := &ProxyInformationResponse{
|
|
||||||
Exists: true,
|
|
||||||
SourceIP: "192.168.0.139",
|
|
||||||
SourcePort: 19132,
|
|
||||||
DestPort: 19132,
|
|
||||||
Protocol: "tcp",
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyInformationResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.Exists != commandUnmarshalled.Exists {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("Exists's are not equal (orig: '%t', unmsh: '%t')", commandInput.Exists, commandUnmarshalled.Exists)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.SourceIP != commandUnmarshalled.SourceIP {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.SourcePort != commandUnmarshalled.SourcePort {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("SourcePort's are not equal (orig: %d, unmsh: %d)", commandInput.SourcePort, commandUnmarshalled.SourcePort)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.DestPort != commandUnmarshalled.DestPort {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("DestPort's are not equal (orig: %d, unmsh: %d)", commandInput.DestPort, commandUnmarshalled.DestPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.Protocol != commandUnmarshalled.Protocol {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("Protocols are not equal (orig: %s, unmsh: %s)", commandInput.Protocol, commandUnmarshalled.Protocol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyInformationResponseNoExist(t *testing.T) {
|
|
||||||
commandInput := &ProxyInformationResponse{
|
|
||||||
Exists: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyInformationResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.Exists != commandUnmarshalled.Exists {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("Exists's are not equal (orig: '%t', unmsh: '%t')", commandInput.Exists, commandUnmarshalled.Exists)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyConnectionInformationRequest(t *testing.T) {
|
|
||||||
commandInput := &ProxyConnectionInformationRequest{
|
|
||||||
ProxyID: 19132,
|
|
||||||
ConnectionID: 25565,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyConnectionInformationRequest)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ProxyID != commandUnmarshalled.ProxyID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ProxyID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ProxyID, commandUnmarshalled.ProxyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ConnectionID != commandUnmarshalled.ConnectionID {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ConnectionID's are not equal (orig: '%d', unmsh: '%d')", commandInput.ConnectionID, commandUnmarshalled.ConnectionID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyConnectionInformationResponseExists(t *testing.T) {
|
|
||||||
commandInput := &ProxyConnectionInformationResponse{
|
|
||||||
Exists: true,
|
|
||||||
ClientIP: "192.168.0.139",
|
|
||||||
ClientPort: 19132,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyConnectionInformationResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.Exists != commandUnmarshalled.Exists {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("Exists's are not equal (orig: '%t', unmsh: '%t')", commandInput.Exists, commandUnmarshalled.Exists)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ClientIP != commandUnmarshalled.ClientIP {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.ClientIP, commandUnmarshalled.ClientIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.ClientPort != commandUnmarshalled.ClientPort {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("ClientPort's are not equal (orig: %d, unmsh: %d)", commandInput.ClientPort, commandUnmarshalled.ClientPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyConnectionInformationResponseNoExists(t *testing.T) {
|
|
||||||
commandInput := &ProxyConnectionInformationResponse{
|
|
||||||
Exists: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandMarshalled, err := Marshal(commandInput)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if logLevel == "debug" {
|
|
||||||
log.Printf("Generated array contents: %v", commandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(commandMarshalled)
|
|
||||||
commandUnmarshalledRaw, err := Unmarshal(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
commandUnmarshalled, ok := commandUnmarshalledRaw.(*ProxyConnectionInformationResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed typecast")
|
|
||||||
}
|
|
||||||
|
|
||||||
if commandInput.Exists != commandUnmarshalled.Exists {
|
|
||||||
t.Fail()
|
|
||||||
log.Printf("Exists's are not equal (orig: '%t', unmsh: '%t')", commandInput.Exists, commandUnmarshalled.Exists)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,422 +0,0 @@
|
||||||
package datacommands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Unmarshal reads from the provided connection and returns
|
|
||||||
// the message type (as a string), the unmarshalled struct, or an error.
|
|
||||||
func Unmarshal(conn io.Reader) (interface{}, error) {
|
|
||||||
// Every command starts with a 1-byte command ID.
|
|
||||||
header := make([]byte, 1)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, header); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read command ID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdID := header[0]
|
|
||||||
switch cmdID {
|
|
||||||
// ProxyStatusRequest: 1 byte ID + 2 bytes ProxyID.
|
|
||||||
case ProxyStatusRequestID:
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyStatusRequest ProxyID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf)
|
|
||||||
|
|
||||||
return &ProxyStatusRequest{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// ProxyStatusResponse: 1 byte ID + 2 bytes ProxyID + 1 byte IsActive.
|
|
||||||
case ProxyStatusResponseID:
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyStatusResponse ProxyID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf)
|
|
||||||
boolBuf := make([]byte, 1)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, boolBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyStatusResponse IsActive: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
isActive := boolBuf[0] != 0
|
|
||||||
|
|
||||||
return &ProxyStatusResponse{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
IsActive: isActive,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// RemoveProxy: 1 byte ID + 2 bytes ProxyID.
|
|
||||||
case RemoveProxyID:
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read RemoveProxy ProxyID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf)
|
|
||||||
|
|
||||||
return &RemoveProxy{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// ProxyConnectionsRequest: 1 byte ID + 2 bytes ProxyID.
|
|
||||||
case ProxyConnectionsRequestID:
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyConnectionsRequest ProxyID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf)
|
|
||||||
|
|
||||||
return &ProxyConnectionsRequest{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// ProxyConnectionsResponse: 1 byte ID + 2 bytes Connections length + 2 bytes for each Connection in Connections.
|
|
||||||
case ProxyConnectionsResponseID:
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyConnectionsResponse length: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
length := binary.BigEndian.Uint16(buf)
|
|
||||||
connections := make([]uint16, length)
|
|
||||||
|
|
||||||
var failedDuringReading error
|
|
||||||
|
|
||||||
for connectionIndex := range connections {
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
failedDuringReading = fmt.Errorf("couldn't read ProxyConnectionsResponse with position of %d: %w", connectionIndex, err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
connections[connectionIndex] = binary.BigEndian.Uint16(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ProxyConnectionsResponse{
|
|
||||||
Connections: connections,
|
|
||||||
}, failedDuringReading
|
|
||||||
|
|
||||||
// ProxyInstanceResponse: 1 byte ID + 2 bytes Proxies length + 2 bytes for each Proxy in Proxies.
|
|
||||||
case ProxyInstanceResponseID:
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyConnectionsResponse length: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
length := binary.BigEndian.Uint16(buf)
|
|
||||||
proxies := make([]uint16, length)
|
|
||||||
|
|
||||||
var failedDuringReading error
|
|
||||||
|
|
||||||
for connectionIndex := range proxies {
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
failedDuringReading = fmt.Errorf("couldn't read ProxyConnectionsResponse with position of %d: %w", connectionIndex, err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
proxies[connectionIndex] = binary.BigEndian.Uint16(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ProxyInstanceResponse{
|
|
||||||
Proxies: proxies,
|
|
||||||
}, failedDuringReading
|
|
||||||
|
|
||||||
// TCPConnectionOpened: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID.
|
|
||||||
case TCPConnectionOpenedID:
|
|
||||||
buf := make([]byte, 2+2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read TCPConnectionOpened fields: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf[0:2])
|
|
||||||
connectionID := binary.BigEndian.Uint16(buf[2:4])
|
|
||||||
|
|
||||||
return &TCPConnectionOpened{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ConnectionID: connectionID,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// TCPConnectionClosed: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID.
|
|
||||||
case TCPConnectionClosedID:
|
|
||||||
buf := make([]byte, 2+2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read TCPConnectionClosed fields: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf[0:2])
|
|
||||||
connectionID := binary.BigEndian.Uint16(buf[2:4])
|
|
||||||
|
|
||||||
return &TCPConnectionClosed{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ConnectionID: connectionID,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// TCPProxyData: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID + 2 bytes DataLength.
|
|
||||||
case TCPProxyDataID:
|
|
||||||
buf := make([]byte, 2+2+2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read TCPProxyData fields: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf[0:2])
|
|
||||||
connectionID := binary.BigEndian.Uint16(buf[2:4])
|
|
||||||
dataLength := binary.BigEndian.Uint16(buf[4:6])
|
|
||||||
|
|
||||||
return &TCPProxyData{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ConnectionID: connectionID,
|
|
||||||
DataLength: dataLength,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// UDPProxyData:
|
|
||||||
// Format: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID +
|
|
||||||
// 1 byte IP version + IP bytes + 2 bytes ClientPort + 2 bytes DataLength.
|
|
||||||
case UDPProxyDataID:
|
|
||||||
// Read 2 bytes ProxyID + 2 bytes ConnectionID.
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read UDPProxyData ProxyID/ConnectionID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf)
|
|
||||||
|
|
||||||
// Read IP version.
|
|
||||||
ipVerBuf := make([]byte, 1)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, ipVerBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read UDPProxyData IP version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipSize int
|
|
||||||
|
|
||||||
if ipVerBuf[0] == 4 {
|
|
||||||
ipSize = IPv4Size
|
|
||||||
} else if ipVerBuf[0] == 6 {
|
|
||||||
ipSize = IPv6Size
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("invalid IP version received: %v", ipVerBuf[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the IP bytes.
|
|
||||||
ipBytes := make([]byte, ipSize)
|
|
||||||
if _, err := io.ReadFull(conn, ipBytes); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read UDPProxyData IP bytes: %w", err)
|
|
||||||
}
|
|
||||||
clientIP := net.IP(ipBytes).String()
|
|
||||||
|
|
||||||
// Read ClientPort.
|
|
||||||
portBuf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, portBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read UDPProxyData ClientPort: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
clientPort := binary.BigEndian.Uint16(portBuf)
|
|
||||||
|
|
||||||
// Read DataLength.
|
|
||||||
dataLengthBuf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, dataLengthBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read UDPProxyData DataLength: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dataLength := binary.BigEndian.Uint16(dataLengthBuf)
|
|
||||||
|
|
||||||
return &UDPProxyData{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ClientIP: clientIP,
|
|
||||||
ClientPort: clientPort,
|
|
||||||
DataLength: dataLength,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// ProxyInformationRequest: 1 byte ID + 2 bytes ProxyID.
|
|
||||||
case ProxyInformationRequestID:
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyInformationRequest ProxyID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf)
|
|
||||||
|
|
||||||
return &ProxyInformationRequest{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// ProxyInformationResponse:
|
|
||||||
// Format: 1 byte ID + 1 byte Exists +
|
|
||||||
// 1 byte IP version + IP bytes + 2 bytes SourcePort + 2 bytes DestPort + 1 byte Protocol.
|
|
||||||
case ProxyInformationResponseID:
|
|
||||||
// Read Exists flag.
|
|
||||||
boolBuf := make([]byte, 1)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, boolBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyInformationResponse Exists flag: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exists := boolBuf[0] != 0
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return &ProxyInformationResponse{
|
|
||||||
Exists: exists,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read IP version.
|
|
||||||
ipVerBuf := make([]byte, 1)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, ipVerBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyInformationResponse IP version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipSize int
|
|
||||||
|
|
||||||
if ipVerBuf[0] == 4 {
|
|
||||||
ipSize = IPv4Size
|
|
||||||
} else if ipVerBuf[0] == 6 {
|
|
||||||
ipSize = IPv6Size
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("invalid IP version in ProxyInformationResponse: %v", ipVerBuf[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the source IP bytes.
|
|
||||||
ipBytes := make([]byte, ipSize)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, ipBytes); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyInformationResponse IP bytes: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceIP := net.IP(ipBytes).String()
|
|
||||||
|
|
||||||
// Read SourcePort and DestPort.
|
|
||||||
portsBuf := make([]byte, 2+2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, portsBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyInformationResponse ports: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sourcePort := binary.BigEndian.Uint16(portsBuf[0:2])
|
|
||||||
destPort := binary.BigEndian.Uint16(portsBuf[2:4])
|
|
||||||
|
|
||||||
// Read protocol.
|
|
||||||
protoBuf := make([]byte, 1)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, protoBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyInformationResponse protocol: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var protocol string
|
|
||||||
|
|
||||||
if protoBuf[0] == TCP {
|
|
||||||
protocol = "tcp"
|
|
||||||
} else if protoBuf[0] == UDP {
|
|
||||||
protocol = "udp"
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("invalid protocol value in ProxyInformationResponse: %d", protoBuf[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ProxyInformationResponse{
|
|
||||||
Exists: exists,
|
|
||||||
SourceIP: sourceIP,
|
|
||||||
SourcePort: sourcePort,
|
|
||||||
DestPort: destPort,
|
|
||||||
Protocol: protocol,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// ProxyConnectionInformationRequest: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID.
|
|
||||||
case ProxyConnectionInformationRequestID:
|
|
||||||
buf := make([]byte, 2+2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyConnectionInformationRequest fields: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyID := binary.BigEndian.Uint16(buf[0:2])
|
|
||||||
connectionID := binary.BigEndian.Uint16(buf[2:4])
|
|
||||||
|
|
||||||
return &ProxyConnectionInformationRequest{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ConnectionID: connectionID,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// ProxyConnectionInformationResponse:
|
|
||||||
// Format: 1 byte ID + 1 byte Exists + 1 byte IP version + IP bytes + 2 bytes ClientPort.
|
|
||||||
case ProxyConnectionInformationResponseID:
|
|
||||||
// Read Exists flag.
|
|
||||||
boolBuf := make([]byte, 1)
|
|
||||||
if _, err := io.ReadFull(conn, boolBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyConnectionInformationResponse Exists flag: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exists := boolBuf[0] != 0
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return &ProxyConnectionInformationResponse{
|
|
||||||
Exists: exists,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read IP version.
|
|
||||||
ipVerBuf := make([]byte, 1)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, ipVerBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyConnectionInformationResponse IP version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipVerBuf[0] != 4 && ipVerBuf[0] != 6 {
|
|
||||||
return nil, fmt.Errorf("invalid IP version in ProxyConnectionInformationResponse: %v", ipVerBuf[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipSize int
|
|
||||||
|
|
||||||
if ipVerBuf[0] == 4 {
|
|
||||||
ipSize = IPv4Size
|
|
||||||
} else {
|
|
||||||
ipSize = IPv6Size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read IP bytes.
|
|
||||||
ipBytes := make([]byte, ipSize)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, ipBytes); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyConnectionInformationResponse IP bytes: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
clientIP := net.IP(ipBytes).String()
|
|
||||||
|
|
||||||
// Read ClientPort.
|
|
||||||
portBuf := make([]byte, 2)
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, portBuf); err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read ProxyConnectionInformationResponse ClientPort: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
clientPort := binary.BigEndian.Uint16(portBuf)
|
|
||||||
|
|
||||||
return &ProxyConnectionInformationResponse{
|
|
||||||
Exists: exists,
|
|
||||||
ClientIP: clientIP,
|
|
||||||
ClientPort: clientPort,
|
|
||||||
}, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown command id: %v", cmdID)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package gaslighter
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
type Gaslighter struct {
|
|
||||||
Byte byte
|
|
||||||
HasGaslit bool
|
|
||||||
ProxiedReader io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gaslighter *Gaslighter) Read(p []byte) (n int, err error) {
|
|
||||||
if gaslighter.HasGaslit {
|
|
||||||
return gaslighter.ProxiedReader.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p[0] = gaslighter.Byte
|
|
||||||
gaslighter.HasGaslit = true
|
|
||||||
|
|
||||||
if len(p) > 1 {
|
|
||||||
n, err := gaslighter.ProxiedReader.Read(p[1:])
|
|
||||||
|
|
||||||
return n + 1, err
|
|
||||||
} else {
|
|
||||||
return 1, nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed remote-bin
|
|
||||||
var binFiles embed.FS
|
|
|
@ -1,23 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WriteLogger struct{}
|
|
||||||
|
|
||||||
func (writer WriteLogger) Write(p []byte) (n int, err error) {
|
|
||||||
logSplit := strings.Split(string(p), "\n")
|
|
||||||
|
|
||||||
for _, line := range logSplit {
|
|
||||||
if line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Process: %s", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(p), err
|
|
||||||
}
|
|
|
@ -1,868 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/rand/v2"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/backendutil"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/datacommands"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/gaslighter"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/local-code/porttranslation"
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/pkg/sftp"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
var validatorInstance *validator.Validate
|
|
||||||
|
|
||||||
type TCPProxy struct {
|
|
||||||
proxyInformation *commonbackend.AddProxy
|
|
||||||
connections map[uint16]net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
type UDPProxy struct {
|
|
||||||
proxyInformation *commonbackend.AddProxy
|
|
||||||
portTranslation *porttranslation.PortTranslation
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSHAppBackendData struct {
|
|
||||||
IP string `json:"ip" validate:"required"`
|
|
||||||
Port uint16 `json:"port" validate:"required"`
|
|
||||||
Username string `json:"username" validate:"required"`
|
|
||||||
PrivateKey string `json:"privateKey" validate:"required"`
|
|
||||||
ListenOnIPs []string `json:"listenOnIPs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSHAppBackend struct {
|
|
||||||
config *SSHAppBackendData
|
|
||||||
conn *ssh.Client
|
|
||||||
listener net.Listener
|
|
||||||
currentSock net.Conn
|
|
||||||
|
|
||||||
tcpProxies map[uint16]*TCPProxy
|
|
||||||
udpProxies map[uint16]*UDPProxy
|
|
||||||
|
|
||||||
// globalNonCriticalMessageLock: Locks all messages that don't need low-latency transmissions & high
|
|
||||||
// speed behind a lock. This ensures safety when it comes to handling messages correctly.
|
|
||||||
globalNonCriticalMessageLock sync.Mutex
|
|
||||||
// globalNonCriticalMessageChan: Channel for handling messages that need a reply / aren't critical.
|
|
||||||
globalNonCriticalMessageChan chan interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
|
||||||
log.Info("SSHAppBackend is initializing...")
|
|
||||||
|
|
||||||
if validatorInstance == nil {
|
|
||||||
validatorInstance = validator.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.globalNonCriticalMessageChan = make(chan interface{})
|
|
||||||
backend.tcpProxies = map[uint16]*TCPProxy{}
|
|
||||||
backend.udpProxies = map[uint16]*UDPProxy{}
|
|
||||||
|
|
||||||
var backendData SSHAppBackendData
|
|
||||||
|
|
||||||
if err := json.Unmarshal(configBytes, &backendData); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validatorInstance.Struct(&backendData); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.config = &backendData
|
|
||||||
|
|
||||||
if len(backend.config.ListenOnIPs) == 0 {
|
|
||||||
backend.config.ListenOnIPs = []string{"0.0.0.0"}
|
|
||||||
}
|
|
||||||
|
|
||||||
signer, err := ssh.ParsePrivateKey([]byte(backendData.PrivateKey))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to initialize: %s", err.Error())
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
auth := ssh.PublicKeys(signer)
|
|
||||||
|
|
||||||
config := &ssh.ClientConfig{
|
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
||||||
User: backendData.Username,
|
|
||||||
Auth: []ssh.AuthMethod{
|
|
||||||
auth,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", backendData.IP, backendData.Port), config)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to initialize: %s", err.Error())
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.conn = conn
|
|
||||||
|
|
||||||
log.Debug("SSHAppBackend has connected successfully.")
|
|
||||||
log.Debug("Getting CPU architecture...")
|
|
||||||
|
|
||||||
session, err := backend.conn.NewSession()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to create session: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdoutBuf bytes.Buffer
|
|
||||||
session.Stdout = &stdoutBuf
|
|
||||||
|
|
||||||
err = session.Run("uname -m")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to run uname command: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuArchBytes := make([]byte, stdoutBuf.Len())
|
|
||||||
stdoutBuf.Read(cpuArchBytes)
|
|
||||||
|
|
||||||
cpuArch := string(cpuArchBytes)
|
|
||||||
cpuArch = cpuArch[:len(cpuArch)-1]
|
|
||||||
|
|
||||||
var backendBinary string
|
|
||||||
|
|
||||||
// Ordered in (subjective) popularity
|
|
||||||
if cpuArch == "x86_64" {
|
|
||||||
backendBinary = "remote-bin/rt-amd64"
|
|
||||||
} else if cpuArch == "aarch64" {
|
|
||||||
backendBinary = "remote-bin/rt-arm64"
|
|
||||||
} else if cpuArch == "arm" {
|
|
||||||
backendBinary = "remote-bin/rt-arm"
|
|
||||||
} else if len(cpuArch) == 4 && string(cpuArch[0]) == "i" && strings.HasSuffix(cpuArch, "86") {
|
|
||||||
backendBinary = "remote-bin/rt-386"
|
|
||||||
} else {
|
|
||||||
log.Warn("Failed to determine executable to use: CPU architecture not compiled/supported currently")
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, fmt.Errorf("CPU architecture not compiled/supported currently")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Checking if we need to copy the application...")
|
|
||||||
|
|
||||||
var binary []byte
|
|
||||||
needsToCopyBinary := true
|
|
||||||
|
|
||||||
session, err = backend.conn.NewSession()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to create session: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
session.Stdout = &stdoutBuf
|
|
||||||
|
|
||||||
err = session.Start("[ -f /tmp/sshappbackend.runtime ] && md5sum /tmp/sshappbackend.runtime | cut -d \" \" -f 1")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to calculate hash of possibly existing backend: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileExists := stdoutBuf.Len() != 0
|
|
||||||
|
|
||||||
if fileExists {
|
|
||||||
remoteMD5HashStringBuf := make([]byte, stdoutBuf.Len())
|
|
||||||
stdoutBuf.Read(remoteMD5HashStringBuf)
|
|
||||||
|
|
||||||
remoteMD5HashString := string(remoteMD5HashStringBuf)
|
|
||||||
remoteMD5HashString = remoteMD5HashString[:len(remoteMD5HashString)-1]
|
|
||||||
|
|
||||||
remoteMD5Hash, err := hex.DecodeString(remoteMD5HashString)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to decode hex: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
binary, err = binFiles.ReadFile(backendBinary)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to read file in the embedded FS: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, fmt.Errorf("(embedded FS): %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
localMD5Hash := md5.Sum(binary)
|
|
||||||
|
|
||||||
log.Infof("remote: %s, local: %s", remoteMD5HashString, hex.EncodeToString(localMD5Hash[:]))
|
|
||||||
|
|
||||||
if bytes.Compare(localMD5Hash[:], remoteMD5Hash) == 0 {
|
|
||||||
needsToCopyBinary = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsToCopyBinary {
|
|
||||||
log.Debug("Copying binary...")
|
|
||||||
|
|
||||||
sftpInstance, err := sftp.NewClient(conn)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to initialize SFTP: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer sftpInstance.Close()
|
|
||||||
|
|
||||||
if len(binary) == 0 {
|
|
||||||
binary, err = binFiles.ReadFile(backendBinary)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to read file in the embedded FS: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, fmt.Errorf("(embedded FS): %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var file *sftp.File
|
|
||||||
|
|
||||||
if fileExists {
|
|
||||||
file, err = sftpInstance.OpenFile("/tmp/sshappbackend.runtime", os.O_WRONLY)
|
|
||||||
} else {
|
|
||||||
file, err = sftpInstance.Create("/tmp/sshappbackend.runtime")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to create (or open) file: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.Write(binary)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to write file: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = file.Chmod(0755)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to change permissions on file: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Done copying file.")
|
|
||||||
sftpInstance.Close()
|
|
||||||
} else {
|
|
||||||
log.Debug("Skipping copying as there's a copy on disk already.")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Initializing Unix socket...")
|
|
||||||
|
|
||||||
socketPath := fmt.Sprintf("/tmp/sock-%d.sock", rand.Uint())
|
|
||||||
listener, err := conn.ListenUnix(socketPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to listen on socket: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Starting process...")
|
|
||||||
|
|
||||||
session, err = backend.conn.NewSession()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to create session: %s", err.Error())
|
|
||||||
conn.Close()
|
|
||||||
backend.conn = nil
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.listener = listener
|
|
||||||
|
|
||||||
session.Stdout = WriteLogger{}
|
|
||||||
session.Stderr = WriteLogger{}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
err := session.Run(fmt.Sprintf("HERMES_LOG_LEVEL=\"%s\" HERMES_API_SOCK=\"%s\" /tmp/sshappbackend.runtime", os.Getenv("HERMES_LOG_LEVEL"), socketPath))
|
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, &ssh.ExitError{}) && !errors.Is(err, &ssh.ExitMissingError{}) {
|
|
||||||
log.Errorf("Critically failed during execution of remote code: %s", err.Error())
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
log.Warn("Remote code failed for an unknown reason. Restarting...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go backend.sockServerHandler()
|
|
||||||
|
|
||||||
log.Debug("Started process. Waiting for Unix socket connection...")
|
|
||||||
|
|
||||||
for backend.currentSock == nil {
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Detected connection. Sending initialization command...")
|
|
||||||
|
|
||||||
proxyStatusRaw, err := backend.SendNonCriticalMessage(&commonbackend.Start{
|
|
||||||
Arguments: []byte{},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyStatus, ok := proxyStatusRaw.(*commonbackend.BackendStatusResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return false, fmt.Errorf("recieved invalid response type: %T", proxyStatusRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxyStatus.StatusCode == commonbackend.StatusFailure {
|
|
||||||
if proxyStatus.Message == "" {
|
|
||||||
return false, fmt.Errorf("failed to initialize backend in remote code")
|
|
||||||
} else {
|
|
||||||
return false, fmt.Errorf("failed to initialize backend in remote code: %s", proxyStatus.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("SSHAppBackend has initialized successfully.")
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) StopBackend() (bool, error) {
|
|
||||||
err := backend.conn.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) GetBackendStatus() (bool, error) {
|
|
||||||
return backend.conn != nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) StartProxy(command *commonbackend.AddProxy) (bool, error) {
|
|
||||||
proxyStatusRaw, err := backend.SendNonCriticalMessage(command)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyStatus, ok := proxyStatusRaw.(*datacommands.ProxyStatusResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return false, fmt.Errorf("recieved invalid response type: %T", proxyStatusRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !proxyStatus.IsActive {
|
|
||||||
return false, fmt.Errorf("failed to initialize proxy in remote code")
|
|
||||||
}
|
|
||||||
|
|
||||||
if command.Protocol == "tcp" {
|
|
||||||
backend.tcpProxies[proxyStatus.ProxyID] = &TCPProxy{
|
|
||||||
proxyInformation: command,
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.tcpProxies[proxyStatus.ProxyID].connections = map[uint16]net.Conn{}
|
|
||||||
} else if command.Protocol == "udp" {
|
|
||||||
backend.udpProxies[proxyStatus.ProxyID] = &UDPProxy{
|
|
||||||
proxyInformation: command,
|
|
||||||
portTranslation: &porttranslation.PortTranslation{},
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.udpProxies[proxyStatus.ProxyID].portTranslation.UDPAddr = &net.UDPAddr{
|
|
||||||
IP: net.ParseIP(command.SourceIP),
|
|
||||||
Port: int(command.SourcePort),
|
|
||||||
}
|
|
||||||
|
|
||||||
udpMessageCommand := &datacommands.UDPProxyData{}
|
|
||||||
udpMessageCommand.ProxyID = proxyStatus.ProxyID
|
|
||||||
|
|
||||||
backend.udpProxies[proxyStatus.ProxyID].portTranslation.WriteFrom = func(ip string, port uint16, data []byte) {
|
|
||||||
udpMessageCommand.ClientIP = ip
|
|
||||||
udpMessageCommand.ClientPort = port
|
|
||||||
udpMessageCommand.DataLength = uint16(len(data))
|
|
||||||
|
|
||||||
marshalledCommand, err := datacommands.Marshal(udpMessageCommand)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to marshal UDP message header")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := backend.currentSock.Write(marshalledCommand); err != nil {
|
|
||||||
log.Warnf("Failed to write UDP message header")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := backend.currentSock.Write(data); err != nil {
|
|
||||||
log.Warnf("Failed to write UDP message")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(3 * time.Minute)
|
|
||||||
|
|
||||||
// Checks if the proxy still exists before continuing
|
|
||||||
_, ok := backend.udpProxies[proxyStatus.ProxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then attempt to run cleanup tasks
|
|
||||||
log.Debug("Running UDP proxy cleanup tasks (invoking CleanupPorts() on portTranslation)")
|
|
||||||
backend.udpProxies[proxyStatus.ProxyID].portTranslation.CleanupPorts()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) StopProxy(command *commonbackend.RemoveProxy) (bool, error) {
|
|
||||||
if command.Protocol == "tcp" {
|
|
||||||
for proxyIndex, proxy := range backend.tcpProxies {
|
|
||||||
if proxy.proxyInformation.DestPort != command.DestPort {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
onDisconnect := &datacommands.TCPConnectionClosed{
|
|
||||||
ProxyID: proxyIndex,
|
|
||||||
}
|
|
||||||
|
|
||||||
for connectionIndex, connection := range proxy.connections {
|
|
||||||
connection.Close()
|
|
||||||
delete(proxy.connections, connectionIndex)
|
|
||||||
|
|
||||||
onDisconnect.ConnectionID = connectionIndex
|
|
||||||
disconnectionCommandMarshalled, err := datacommands.Marshal(onDisconnect)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to marshal disconnection message: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.currentSock.Write(disconnectionCommandMarshalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyStatusRaw, err := backend.SendNonCriticalMessage(&datacommands.RemoveProxy{
|
|
||||||
ProxyID: proxyIndex,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyStatus, ok := proxyStatusRaw.(*datacommands.ProxyStatusResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("Failed to stop proxy: typecast failed")
|
|
||||||
return true, fmt.Errorf("failed to stop proxy: typecast failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxyStatus.IsActive {
|
|
||||||
log.Warn("Failed to stop proxy: still running")
|
|
||||||
return true, fmt.Errorf("failed to stop proxy: still running")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if command.Protocol == "udp" {
|
|
||||||
for proxyIndex, proxy := range backend.udpProxies {
|
|
||||||
if proxy.proxyInformation.DestPort != command.DestPort {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyStatusRaw, err := backend.SendNonCriticalMessage(&datacommands.RemoveProxy{
|
|
||||||
ProxyID: proxyIndex,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyStatus, ok := proxyStatusRaw.(*datacommands.ProxyStatusResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("Failed to stop proxy: typecast failed")
|
|
||||||
return true, fmt.Errorf("failed to stop proxy: typecast failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxyStatus.IsActive {
|
|
||||||
log.Warn("Failed to stop proxy: still running")
|
|
||||||
return true, fmt.Errorf("failed to stop proxy: still running")
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy.portTranslation.StopAllPorts()
|
|
||||||
delete(backend.udpProxies, proxyIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, fmt.Errorf("could not find the proxy")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) GetAllClientConnections() []*commonbackend.ProxyClientConnection {
|
|
||||||
connections := []*commonbackend.ProxyClientConnection{}
|
|
||||||
informationRequest := &datacommands.ProxyConnectionInformationRequest{}
|
|
||||||
|
|
||||||
for proxyID, tcpProxy := range backend.tcpProxies {
|
|
||||||
informationRequest.ProxyID = proxyID
|
|
||||||
|
|
||||||
for connectionID := range tcpProxy.connections {
|
|
||||||
informationRequest.ConnectionID = connectionID
|
|
||||||
|
|
||||||
proxyStatusRaw, err := backend.SendNonCriticalMessage(informationRequest)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to get connection information for Proxy ID: %d, Connection ID: %d: %s", proxyID, connectionID, err.Error())
|
|
||||||
return connections
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionStatus, ok := proxyStatusRaw.(*datacommands.ProxyConnectionInformationResponse)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("Failed to get connection response: typecast failed")
|
|
||||||
return connections
|
|
||||||
}
|
|
||||||
|
|
||||||
if !connectionStatus.Exists {
|
|
||||||
log.Warnf("Connection with proxy ID: %d, Connection ID: %d is reported to not exist!", proxyID, connectionID)
|
|
||||||
tcpProxy.connections[connectionID].Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
connections = append(connections, &commonbackend.ProxyClientConnection{
|
|
||||||
SourceIP: tcpProxy.proxyInformation.SourceIP,
|
|
||||||
SourcePort: tcpProxy.proxyInformation.SourcePort,
|
|
||||||
DestPort: tcpProxy.proxyInformation.DestPort,
|
|
||||||
ClientIP: connectionStatus.ClientIP,
|
|
||||||
ClientPort: connectionStatus.ClientPort,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return connections
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't have any parameter limitations, so we should be good.
|
|
||||||
func (backend *SSHAppBackend) CheckParametersForConnections(clientParameters *commonbackend.CheckClientParameters) *commonbackend.CheckParametersResponse {
|
|
||||||
return &commonbackend.CheckParametersResponse{
|
|
||||||
IsValid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
|
|
||||||
var backendData SSHAppBackendData
|
|
||||||
|
|
||||||
if validatorInstance == nil {
|
|
||||||
validatorInstance = validator.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(arguments, &backendData); err != nil {
|
|
||||||
return &commonbackend.CheckParametersResponse{
|
|
||||||
IsValid: false,
|
|
||||||
Message: fmt.Sprintf("could not read json: %s", err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validatorInstance.Struct(&backendData); err != nil {
|
|
||||||
return &commonbackend.CheckParametersResponse{
|
|
||||||
IsValid: false,
|
|
||||||
Message: fmt.Sprintf("failed validation of parameters: %s", err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &commonbackend.CheckParametersResponse{
|
|
||||||
IsValid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) OnTCPConnectionOpened(proxyID, connectionID uint16) {
|
|
||||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", backend.tcpProxies[proxyID].proxyInformation.SourceIP, backend.tcpProxies[proxyID].proxyInformation.SourcePort))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to dial sock: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
dataBuf := make([]byte, 65535)
|
|
||||||
|
|
||||||
tcpData := &datacommands.TCPProxyData{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ConnectionID: connectionID,
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
len, err := conn.Read(dataBuf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, net.ErrClosed) {
|
|
||||||
return
|
|
||||||
} else if err.Error() != "EOF" {
|
|
||||||
log.Warnf("failed to read from sock: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpData.DataLength = uint16(len)
|
|
||||||
marshalledMessageCommand, err := datacommands.Marshal(tcpData)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to marshal message data: %s", err.Error())
|
|
||||||
|
|
||||||
conn.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := backend.currentSock.Write(marshalledMessageCommand); err != nil {
|
|
||||||
log.Warnf("failed to send marshalled message data: %s", err.Error())
|
|
||||||
|
|
||||||
conn.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := backend.currentSock.Write(dataBuf[:len]); err != nil {
|
|
||||||
log.Warnf("failed to send raw message data: %s", err.Error())
|
|
||||||
|
|
||||||
conn.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDisconnect := &datacommands.TCPConnectionClosed{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ConnectionID: connectionID,
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectionCommandMarshalled, err := datacommands.Marshal(onDisconnect)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to marshal disconnection message: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.currentSock.Write(disconnectionCommandMarshalled)
|
|
||||||
}()
|
|
||||||
|
|
||||||
backend.tcpProxies[proxyID].connections[connectionID] = conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) OnTCPConnectionClosed(proxyID, connectionID uint16) {
|
|
||||||
proxy, ok := backend.tcpProxies[proxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("Could not find TCP proxy")
|
|
||||||
}
|
|
||||||
|
|
||||||
connection, ok := proxy.connections[connectionID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("Could not find connection in TCP proxy")
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.Close()
|
|
||||||
delete(proxy.connections, connectionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) HandleTCPMessage(message *datacommands.TCPProxyData, data []byte) {
|
|
||||||
proxy, ok := backend.tcpProxies[message.ProxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("Could not find TCP proxy")
|
|
||||||
}
|
|
||||||
|
|
||||||
connection, ok := proxy.connections[message.ConnectionID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("Could not find connection in TCP proxy")
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) HandleUDPMessage(message *datacommands.UDPProxyData, data []byte) {
|
|
||||||
proxy, ok := backend.udpProxies[message.ProxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("Could not find UDP proxy")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := proxy.portTranslation.WriteTo(message.ClientIP, message.ClientPort, data); err != nil {
|
|
||||||
log.Warnf("Failed to write to UDP: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) SendNonCriticalMessage(iface interface{}) (interface{}, error) {
|
|
||||||
if backend.currentSock == nil {
|
|
||||||
return nil, fmt.Errorf("socket connection not initialized yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := datacommands.Marshal(iface)
|
|
||||||
|
|
||||||
if err != nil && err.Error() == "unsupported command type" {
|
|
||||||
bytes, err = commonbackend.Marshal(iface)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.globalNonCriticalMessageLock.Lock()
|
|
||||||
|
|
||||||
if _, err := backend.currentSock.Write(bytes); err != nil {
|
|
||||||
backend.globalNonCriticalMessageLock.Unlock()
|
|
||||||
return nil, fmt.Errorf("failed to write message: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
reply, ok := <-backend.globalNonCriticalMessageChan
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
backend.globalNonCriticalMessageLock.Unlock()
|
|
||||||
return nil, fmt.Errorf("failed to get reply back: chan not OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.globalNonCriticalMessageLock.Unlock()
|
|
||||||
return reply, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHAppBackend) sockServerHandler() {
|
|
||||||
for {
|
|
||||||
conn, err := backend.listener.Accept()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to accept remote connection: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Successfully connected.")
|
|
||||||
|
|
||||||
backend.currentSock = conn
|
|
||||||
|
|
||||||
commandID := make([]byte, 1)
|
|
||||||
|
|
||||||
gaslighter := &gaslighter.Gaslighter{}
|
|
||||||
gaslighter.ProxiedReader = conn
|
|
||||||
|
|
||||||
dataBuffer := make([]byte, 65535)
|
|
||||||
|
|
||||||
var commandRaw interface{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if _, err := conn.Read(commandID); err != nil {
|
|
||||||
log.Warnf("Failed to read command ID: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gaslighter.Byte = commandID[0]
|
|
||||||
gaslighter.HasGaslit = false
|
|
||||||
|
|
||||||
if gaslighter.Byte > 100 {
|
|
||||||
commandRaw, err = datacommands.Unmarshal(gaslighter)
|
|
||||||
} else {
|
|
||||||
commandRaw, err = commonbackend.Unmarshal(gaslighter)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to parse command: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
switch command := commandRaw.(type) {
|
|
||||||
case *datacommands.TCPConnectionOpened:
|
|
||||||
backend.OnTCPConnectionOpened(command.ProxyID, command.ConnectionID)
|
|
||||||
case *datacommands.TCPConnectionClosed:
|
|
||||||
backend.OnTCPConnectionClosed(command.ProxyID, command.ConnectionID)
|
|
||||||
case *datacommands.TCPProxyData:
|
|
||||||
if _, err := io.ReadFull(conn, dataBuffer[:command.DataLength]); err != nil {
|
|
||||||
log.Warnf("Failed to read entire data buffer: %s", err.Error())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.HandleTCPMessage(command, dataBuffer[:command.DataLength])
|
|
||||||
case *datacommands.UDPProxyData:
|
|
||||||
if _, err := io.ReadFull(conn, dataBuffer[:command.DataLength]); err != nil {
|
|
||||||
log.Warnf("Failed to read entire data buffer: %s", err.Error())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.HandleUDPMessage(command, dataBuffer[:command.DataLength])
|
|
||||||
default:
|
|
||||||
select {
|
|
||||||
case backend.globalNonCriticalMessageChan <- command:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
|
||||||
|
|
||||||
if logLevel != "" {
|
|
||||||
switch logLevel {
|
|
||||||
case "debug":
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
|
|
||||||
case "info":
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
|
|
||||||
case "warn":
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
|
|
||||||
case "error":
|
|
||||||
log.SetLevel(log.ErrorLevel)
|
|
||||||
|
|
||||||
case "fatal":
|
|
||||||
log.SetLevel(log.FatalLevel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
backend := &SSHAppBackend{}
|
|
||||||
|
|
||||||
application := backendutil.NewHelper(backend)
|
|
||||||
err := application.Start()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed execution in application: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
package porttranslation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type connectionData struct {
|
|
||||||
udpConn *net.UDPConn
|
|
||||||
buf []byte
|
|
||||||
hasBeenAliveFor time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type PortTranslation struct {
|
|
||||||
UDPAddr *net.UDPAddr
|
|
||||||
WriteFrom func(ip string, port uint16, data []byte)
|
|
||||||
|
|
||||||
newConnectionLock sync.Mutex
|
|
||||||
connections map[string]map[uint16]*connectionData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (translation *PortTranslation) CleanupPorts() {
|
|
||||||
if translation.connections == nil {
|
|
||||||
translation.connections = map[string]map[uint16]*connectionData{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for connectionIPIndex, connectionPorts := range translation.connections {
|
|
||||||
anyAreAlive := false
|
|
||||||
|
|
||||||
for connectionPortIndex, connectionData := range connectionPorts {
|
|
||||||
if time.Now().Before(connectionData.hasBeenAliveFor.Add(3 * time.Minute)) {
|
|
||||||
anyAreAlive = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionData.udpConn.Close()
|
|
||||||
delete(connectionPorts, connectionPortIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !anyAreAlive {
|
|
||||||
delete(translation.connections, connectionIPIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (translation *PortTranslation) StopAllPorts() {
|
|
||||||
if translation.connections == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for connectionIPIndex, connectionPorts := range translation.connections {
|
|
||||||
for connectionPortIndex, connectionData := range connectionPorts {
|
|
||||||
connectionData.udpConn.Close()
|
|
||||||
delete(connectionPorts, connectionPortIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(translation.connections, connectionIPIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
translation.connections = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (translation *PortTranslation) WriteTo(ip string, port uint16, data []byte) (int, error) {
|
|
||||||
if translation.connections == nil {
|
|
||||||
translation.connections = map[string]map[uint16]*connectionData{}
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionPortData, ok := translation.connections[ip]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
translation.connections[ip] = map[uint16]*connectionData{}
|
|
||||||
connectionPortData = translation.connections[ip]
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionStruct, ok := connectionPortData[port]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
connectionPortData[port] = &connectionData{}
|
|
||||||
connectionStruct = connectionPortData[port]
|
|
||||||
|
|
||||||
udpConn, err := net.DialUDP("udp", nil, translation.UDPAddr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to initialize UDP socket: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionStruct.udpConn = udpConn
|
|
||||||
connectionStruct.buf = make([]byte, 65535)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
n, err := udpConn.Read(connectionStruct.buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
udpConn.Close()
|
|
||||||
delete(connectionPortData, port)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionStruct.hasBeenAliveFor = time.Now()
|
|
||||||
translation.WriteFrom(ip, port, connectionStruct.buf[:n])
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionStruct.hasBeenAliveFor = time.Now()
|
|
||||||
return connectionStruct.udpConn.Write(data)
|
|
||||||
}
|
|
|
@ -1,306 +0,0 @@
|
||||||
package backendutil_custom
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/backendutil"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/datacommands"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/gaslighter"
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BackendApplicationHelper struct {
|
|
||||||
Backend BackendInterface
|
|
||||||
SocketPath string
|
|
||||||
|
|
||||||
socket net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (helper *BackendApplicationHelper) Start() error {
|
|
||||||
log.Debug("BackendApplicationHelper is starting")
|
|
||||||
err := backendutil.ConfigureProfiling()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Currently waiting for Unix socket connection...")
|
|
||||||
|
|
||||||
helper.socket, err = net.Dial("unix", helper.SocketPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.Backend.OnSocketConnection(helper.socket)
|
|
||||||
|
|
||||||
log.Debug("Sucessfully connected")
|
|
||||||
|
|
||||||
gaslighter := &gaslighter.Gaslighter{}
|
|
||||||
gaslighter.ProxiedReader = helper.socket
|
|
||||||
|
|
||||||
commandID := make([]byte, 1)
|
|
||||||
|
|
||||||
for {
|
|
||||||
if _, err := helper.socket.Read(commandID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gaslighter.Byte = commandID[0]
|
|
||||||
gaslighter.HasGaslit = false
|
|
||||||
|
|
||||||
var commandRaw interface{}
|
|
||||||
|
|
||||||
if gaslighter.Byte > 100 {
|
|
||||||
commandRaw, err = datacommands.Unmarshal(gaslighter)
|
|
||||||
} else {
|
|
||||||
commandRaw, err = commonbackend.Unmarshal(gaslighter)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch command := commandRaw.(type) {
|
|
||||||
case *datacommands.ProxyConnectionsRequest:
|
|
||||||
connections := helper.Backend.GetAllClientConnections(command.ProxyID)
|
|
||||||
|
|
||||||
serverParams := &datacommands.ProxyConnectionsResponse{
|
|
||||||
Connections: connections,
|
|
||||||
}
|
|
||||||
|
|
||||||
byteData, err := datacommands.Marshal(serverParams)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = helper.socket.Write(byteData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case *datacommands.RemoveProxy:
|
|
||||||
ok, err := helper.Backend.StopProxy(command)
|
|
||||||
var hasAnyFailed bool
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warnf("failed to remove proxy (ID %d): RemoveProxy returned into failure state", command.ProxyID)
|
|
||||||
hasAnyFailed = true
|
|
||||||
} else if err != nil {
|
|
||||||
log.Warnf("failed to remove proxy (ID %d): %s", command.ProxyID, err.Error())
|
|
||||||
hasAnyFailed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &datacommands.ProxyStatusResponse{
|
|
||||||
ProxyID: command.ProxyID,
|
|
||||||
IsActive: hasAnyFailed,
|
|
||||||
}
|
|
||||||
|
|
||||||
responseMarshalled, err := datacommands.Marshal(response)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
|
||||||
case *datacommands.ProxyInformationRequest:
|
|
||||||
response := helper.Backend.ResolveProxy(command.ProxyID)
|
|
||||||
responseMarshalled, err := datacommands.Marshal(response)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
|
||||||
case *datacommands.ProxyConnectionInformationRequest:
|
|
||||||
response := helper.Backend.ResolveConnection(command.ProxyID, command.ConnectionID)
|
|
||||||
responseMarshalled, err := datacommands.Marshal(response)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
|
||||||
case *datacommands.TCPConnectionClosed:
|
|
||||||
helper.Backend.OnTCPConnectionClosed(command.ProxyID, command.ConnectionID)
|
|
||||||
case *datacommands.TCPProxyData:
|
|
||||||
bytes := make([]byte, command.DataLength)
|
|
||||||
_, err := io.ReadFull(helper.socket, bytes)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("failed to read TCP data")
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.Backend.HandleTCPMessage(command, bytes)
|
|
||||||
case *datacommands.UDPProxyData:
|
|
||||||
bytes := make([]byte, command.DataLength)
|
|
||||||
_, err := io.ReadFull(helper.socket, bytes)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("failed to read TCP data")
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.Backend.HandleUDPMessage(command, bytes)
|
|
||||||
case *commonbackend.Start:
|
|
||||||
ok, err := helper.Backend.StartBackend(command.Arguments)
|
|
||||||
|
|
||||||
var (
|
|
||||||
message string
|
|
||||||
statusCode int
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
message = err.Error()
|
|
||||||
statusCode = commonbackend.StatusFailure
|
|
||||||
} else {
|
|
||||||
statusCode = commonbackend.StatusSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &commonbackend.BackendStatusResponse{
|
|
||||||
IsRunning: ok,
|
|
||||||
StatusCode: statusCode,
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
|
|
||||||
responseMarshalled, err := commonbackend.Marshal(response)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
|
||||||
case *commonbackend.Stop:
|
|
||||||
ok, err := helper.Backend.StopBackend()
|
|
||||||
|
|
||||||
var (
|
|
||||||
message string
|
|
||||||
statusCode int
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
message = err.Error()
|
|
||||||
statusCode = commonbackend.StatusFailure
|
|
||||||
} else {
|
|
||||||
statusCode = commonbackend.StatusSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &commonbackend.BackendStatusResponse{
|
|
||||||
IsRunning: !ok,
|
|
||||||
StatusCode: statusCode,
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
|
|
||||||
responseMarshalled, err := commonbackend.Marshal(response)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
|
||||||
case *commonbackend.BackendStatusRequest:
|
|
||||||
ok, err := helper.Backend.GetBackendStatus()
|
|
||||||
|
|
||||||
var (
|
|
||||||
message string
|
|
||||||
statusCode int
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
message = err.Error()
|
|
||||||
statusCode = commonbackend.StatusFailure
|
|
||||||
} else {
|
|
||||||
statusCode = commonbackend.StatusSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &commonbackend.BackendStatusResponse{
|
|
||||||
IsRunning: ok,
|
|
||||||
StatusCode: statusCode,
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
|
|
||||||
responseMarshalled, err := commonbackend.Marshal(response)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
|
||||||
case *commonbackend.AddProxy:
|
|
||||||
id, ok, err := helper.Backend.StartProxy(command)
|
|
||||||
var hasAnyFailed bool
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): StartProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
|
||||||
hasAnyFailed = true
|
|
||||||
} else if err != nil {
|
|
||||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): %s", command.SourceIP, command.SourcePort, command.DestPort, err.Error())
|
|
||||||
hasAnyFailed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &datacommands.ProxyStatusResponse{
|
|
||||||
ProxyID: id,
|
|
||||||
IsActive: !hasAnyFailed,
|
|
||||||
}
|
|
||||||
|
|
||||||
responseMarshalled, err := datacommands.Marshal(response)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to marshal response: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.socket.Write(responseMarshalled)
|
|
||||||
case *commonbackend.CheckClientParameters:
|
|
||||||
resp := helper.Backend.CheckParametersForConnections(command)
|
|
||||||
resp.InResponseTo = "checkClientParameters"
|
|
||||||
|
|
||||||
byteData, err := commonbackend.Marshal(resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = helper.socket.Write(byteData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case *commonbackend.CheckServerParameters:
|
|
||||||
resp := helper.Backend.CheckParametersForBackend(command.Arguments)
|
|
||||||
resp.InResponseTo = "checkServerParameters"
|
|
||||||
|
|
||||||
byteData, err := commonbackend.Marshal(resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = helper.socket.Write(byteData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Warnf("Unsupported command recieved: %T", command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHelper(backend BackendInterface) *BackendApplicationHelper {
|
|
||||||
socketPath, ok := os.LookupEnv("HERMES_API_SOCK")
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("HERMES_API_SOCK is not defined! This will cause an issue unless the backend manually overwrites it")
|
|
||||||
}
|
|
||||||
|
|
||||||
helper := &BackendApplicationHelper{
|
|
||||||
Backend: backend,
|
|
||||||
SocketPath: socketPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
return helper
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package backendutil_custom
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/datacommands"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BackendInterface interface {
|
|
||||||
StartBackend(arguments []byte) (bool, error)
|
|
||||||
StopBackend() (bool, error)
|
|
||||||
GetBackendStatus() (bool, error)
|
|
||||||
StartProxy(command *commonbackend.AddProxy) (uint16, bool, error)
|
|
||||||
StopProxy(command *datacommands.RemoveProxy) (bool, error)
|
|
||||||
GetAllProxies() []uint16
|
|
||||||
ResolveProxy(proxyID uint16) *datacommands.ProxyInformationResponse
|
|
||||||
GetAllClientConnections(proxyID uint16) []uint16
|
|
||||||
ResolveConnection(proxyID, connectionID uint16) *datacommands.ProxyConnectionInformationResponse
|
|
||||||
CheckParametersForConnections(clientParameters *commonbackend.CheckClientParameters) *commonbackend.CheckParametersResponse
|
|
||||||
CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse
|
|
||||||
OnTCPConnectionClosed(proxyID, connectionID uint16)
|
|
||||||
HandleTCPMessage(message *datacommands.TCPProxyData, data []byte)
|
|
||||||
HandleUDPMessage(message *datacommands.UDPProxyData, data []byte)
|
|
||||||
OnSocketConnection(sock net.Conn)
|
|
||||||
}
|
|
|
@ -1,460 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/datacommands"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/remote-code/backendutil_custom"
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TCPProxy struct {
|
|
||||||
connectionIDIndex uint16
|
|
||||||
connectionIDLock sync.Mutex
|
|
||||||
|
|
||||||
proxyInformation *commonbackend.AddProxy
|
|
||||||
connections map[uint16]net.Conn
|
|
||||||
server net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
type UDPProxy struct {
|
|
||||||
server *net.UDPConn
|
|
||||||
proxyInformation *commonbackend.AddProxy
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSHRemoteAppBackend struct {
|
|
||||||
proxyIDIndex uint16
|
|
||||||
proxyIDLock sync.Mutex
|
|
||||||
|
|
||||||
tcpProxies map[uint16]*TCPProxy
|
|
||||||
udpProxies map[uint16]*UDPProxy
|
|
||||||
|
|
||||||
isRunning bool
|
|
||||||
|
|
||||||
sock net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) StartBackend(byte []byte) (bool, error) {
|
|
||||||
backend.tcpProxies = map[uint16]*TCPProxy{}
|
|
||||||
backend.udpProxies = map[uint16]*UDPProxy{}
|
|
||||||
|
|
||||||
backend.isRunning = true
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) StopBackend() (bool, error) {
|
|
||||||
for tcpProxyIndex, tcpProxy := range backend.tcpProxies {
|
|
||||||
for _, tcpConnection := range tcpProxy.connections {
|
|
||||||
tcpConnection.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpProxy.server.Close()
|
|
||||||
delete(backend.tcpProxies, tcpProxyIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
for udpProxyIndex, udpProxy := range backend.udpProxies {
|
|
||||||
udpProxy.server.Close()
|
|
||||||
delete(backend.udpProxies, udpProxyIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.isRunning = false
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) GetBackendStatus() (bool, error) {
|
|
||||||
return backend.isRunning, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) StartProxy(command *commonbackend.AddProxy) (uint16, bool, error) {
|
|
||||||
// Allocate a new proxy ID
|
|
||||||
backend.proxyIDLock.Lock()
|
|
||||||
proxyID := backend.proxyIDIndex
|
|
||||||
backend.proxyIDIndex++
|
|
||||||
backend.proxyIDLock.Unlock()
|
|
||||||
|
|
||||||
if command.Protocol == "tcp" {
|
|
||||||
backend.tcpProxies[proxyID] = &TCPProxy{
|
|
||||||
connections: map[uint16]net.Conn{},
|
|
||||||
proxyInformation: command,
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := net.Listen("tcp", fmt.Sprintf(":%d", command.DestPort))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, false, fmt.Errorf("failed to open server: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.tcpProxies[proxyID].server = server
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
conn, err := server.Accept()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to accept connection: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
backend.tcpProxies[proxyID].connectionIDLock.Lock()
|
|
||||||
connectionID := backend.tcpProxies[proxyID].connectionIDIndex
|
|
||||||
backend.tcpProxies[proxyID].connectionIDIndex++
|
|
||||||
backend.tcpProxies[proxyID].connectionIDLock.Unlock()
|
|
||||||
|
|
||||||
backend.tcpProxies[proxyID].connections[connectionID] = conn
|
|
||||||
|
|
||||||
dataBuf := make([]byte, 65535)
|
|
||||||
|
|
||||||
onConnection := &datacommands.TCPConnectionOpened{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ConnectionID: connectionID,
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionCommandMarshalled, err := datacommands.Marshal(onConnection)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to marshal connection message: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.sock.Write(connectionCommandMarshalled)
|
|
||||||
|
|
||||||
tcpData := &datacommands.TCPProxyData{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ConnectionID: connectionID,
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
len, err := conn.Read(dataBuf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, net.ErrClosed) {
|
|
||||||
return
|
|
||||||
} else if err.Error() != "EOF" {
|
|
||||||
log.Warnf("failed to read from sock: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpData.DataLength = uint16(len)
|
|
||||||
marshalledMessageCommand, err := datacommands.Marshal(tcpData)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to marshal message data: %s", err.Error())
|
|
||||||
|
|
||||||
conn.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := backend.sock.Write(marshalledMessageCommand); err != nil {
|
|
||||||
log.Warnf("failed to send marshalled message data: %s", err.Error())
|
|
||||||
|
|
||||||
conn.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := backend.sock.Write(dataBuf[:len]); err != nil {
|
|
||||||
log.Warnf("failed to send raw message data: %s", err.Error())
|
|
||||||
|
|
||||||
conn.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDisconnect := &datacommands.TCPConnectionClosed{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
ConnectionID: connectionID,
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectionCommandMarshalled, err := datacommands.Marshal(onDisconnect)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to marshal disconnection message: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.sock.Write(disconnectionCommandMarshalled)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
} else if command.Protocol == "udp" {
|
|
||||||
backend.udpProxies[proxyID] = &UDPProxy{
|
|
||||||
proxyInformation: command,
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := net.ListenUDP("udp", &net.UDPAddr{
|
|
||||||
IP: net.IPv4(0, 0, 0, 0),
|
|
||||||
Port: int(command.DestPort),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, false, fmt.Errorf("failed to open server: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.udpProxies[proxyID].server = server
|
|
||||||
dataBuf := make([]byte, 65535)
|
|
||||||
|
|
||||||
udpProxyData := &datacommands.UDPProxyData{
|
|
||||||
ProxyID: proxyID,
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
len, addr, err := server.ReadFromUDP(dataBuf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to read from UDP socket: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
udpProxyData.ClientIP = addr.IP.String()
|
|
||||||
udpProxyData.ClientPort = uint16(addr.Port)
|
|
||||||
udpProxyData.DataLength = uint16(len)
|
|
||||||
|
|
||||||
marshalledMessageCommand, err := datacommands.Marshal(udpProxyData)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to marshal message data: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := backend.sock.Write(marshalledMessageCommand); err != nil {
|
|
||||||
log.Warnf("failed to send marshalled message data: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := backend.sock.Write(dataBuf[:len]); err != nil {
|
|
||||||
log.Warnf("failed to send raw message data: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
return proxyID, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) StopProxy(command *datacommands.RemoveProxy) (bool, error) {
|
|
||||||
tcpProxy, ok := backend.tcpProxies[command.ProxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
udpProxy, ok := backend.udpProxies[command.ProxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return ok, fmt.Errorf("could not find proxy")
|
|
||||||
}
|
|
||||||
|
|
||||||
udpProxy.server.Close()
|
|
||||||
delete(backend.udpProxies, command.ProxyID)
|
|
||||||
} else {
|
|
||||||
for _, tcpConnection := range tcpProxy.connections {
|
|
||||||
tcpConnection.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpProxy.server.Close()
|
|
||||||
delete(backend.tcpProxies, command.ProxyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) GetAllProxies() []uint16 {
|
|
||||||
proxyList := make([]uint16, len(backend.tcpProxies)+len(backend.udpProxies))
|
|
||||||
|
|
||||||
currentPos := 0
|
|
||||||
|
|
||||||
for tcpProxy := range backend.tcpProxies {
|
|
||||||
proxyList[currentPos] = tcpProxy
|
|
||||||
currentPos += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for udpProxy := range backend.udpProxies {
|
|
||||||
proxyList[currentPos] = udpProxy
|
|
||||||
currentPos += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return proxyList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) ResolveProxy(proxyID uint16) *datacommands.ProxyInformationResponse {
|
|
||||||
var proxyInformation *commonbackend.AddProxy
|
|
||||||
response := &datacommands.ProxyInformationResponse{}
|
|
||||||
|
|
||||||
tcpProxy, ok := backend.tcpProxies[proxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
udpProxy, ok := backend.udpProxies[proxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
response.Exists = false
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyInformation = udpProxy.proxyInformation
|
|
||||||
} else {
|
|
||||||
proxyInformation = tcpProxy.proxyInformation
|
|
||||||
}
|
|
||||||
|
|
||||||
response.Exists = true
|
|
||||||
response.SourceIP = proxyInformation.SourceIP
|
|
||||||
response.SourcePort = proxyInformation.SourcePort
|
|
||||||
response.DestPort = proxyInformation.DestPort
|
|
||||||
response.Protocol = proxyInformation.Protocol
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) GetAllClientConnections(proxyID uint16) []uint16 {
|
|
||||||
tcpProxy, ok := backend.tcpProxies[proxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return []uint16{}
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionsArray := make([]uint16, len(tcpProxy.connections))
|
|
||||||
currentPos := 0
|
|
||||||
|
|
||||||
for connectionIndex := range tcpProxy.connections {
|
|
||||||
connectionsArray[currentPos] = connectionIndex
|
|
||||||
currentPos++
|
|
||||||
}
|
|
||||||
|
|
||||||
return connectionsArray
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) ResolveConnection(proxyID, connectionID uint16) *datacommands.ProxyConnectionInformationResponse {
|
|
||||||
response := &datacommands.ProxyConnectionInformationResponse{}
|
|
||||||
tcpProxy, ok := backend.tcpProxies[proxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
response.Exists = false
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
connection, ok := tcpProxy.connections[connectionID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
response.Exists = false
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := connection.RemoteAddr().String()
|
|
||||||
ip := addr[:strings.LastIndex(addr, ":")]
|
|
||||||
port, err := strconv.Atoi(addr[strings.LastIndex(addr, ":")+1:])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to parse client port: %s", err.Error())
|
|
||||||
response.Exists = false
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
response.ClientIP = ip
|
|
||||||
response.ClientPort = uint16(port)
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) CheckParametersForConnections(clientParameters *commonbackend.CheckClientParameters) *commonbackend.CheckParametersResponse {
|
|
||||||
return &commonbackend.CheckParametersResponse{
|
|
||||||
IsValid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
|
|
||||||
return &commonbackend.CheckParametersResponse{
|
|
||||||
IsValid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) HandleTCPMessage(message *datacommands.TCPProxyData, data []byte) {
|
|
||||||
tcpProxy, ok := backend.tcpProxies[message.ProxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warnf("could not find tcp proxy (ID %d)", message.ProxyID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
connection, ok := tcpProxy.connections[message.ConnectionID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warnf("could not find tcp proxy (ID %d) with connection ID (%d)", message.ProxyID, message.ConnectionID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) HandleUDPMessage(message *datacommands.UDPProxyData, data []byte) {
|
|
||||||
udpProxy, ok := backend.udpProxies[message.ProxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
udpProxy.server.WriteToUDP(data, &net.UDPAddr{
|
|
||||||
IP: net.ParseIP(message.ClientIP),
|
|
||||||
Port: int(message.ClientPort),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) OnTCPConnectionClosed(proxyID, connectionID uint16) {
|
|
||||||
tcpProxy, ok := backend.tcpProxies[proxyID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
connection, ok := tcpProxy.connections[connectionID]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.Close()
|
|
||||||
delete(tcpProxy.connections, connectionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHRemoteAppBackend) OnSocketConnection(sock net.Conn) {
|
|
||||||
backend.sock = sock
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
|
||||||
|
|
||||||
if logLevel != "" {
|
|
||||||
switch logLevel {
|
|
||||||
case "debug":
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
|
|
||||||
case "info":
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
|
|
||||||
case "warn":
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
|
|
||||||
case "error":
|
|
||||||
log.SetLevel(log.ErrorLevel)
|
|
||||||
|
|
||||||
case "fatal":
|
|
||||||
log.SetLevel(log.FatalLevel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
backend := &SSHRemoteAppBackend{}
|
|
||||||
|
|
||||||
application := backendutil_custom.NewHelper(backend)
|
|
||||||
err := application.Start()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed execution in application: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,51 +2,20 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/backendutil"
|
"git.terah.dev/imterah/hermes/backendutil"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
var validatorInstance *validator.Validate
|
|
||||||
|
|
||||||
type ConnWithTimeout struct {
|
|
||||||
net.Conn
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
WriteTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWithTimeout) Read(b []byte) (int, error) {
|
|
||||||
err := c.Conn.SetReadDeadline(time.Now().Add(c.ReadTimeout))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWithTimeout) Write(b []byte) (int, error) {
|
|
||||||
err := c.Conn.SetWriteDeadline(time.Now().Add(c.WriteTimeout))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSHListener struct {
|
type SSHListener struct {
|
||||||
SourceIP string
|
SourceIP string
|
||||||
SourcePort uint16
|
SourcePort uint16
|
||||||
|
@ -55,46 +24,31 @@ type SSHListener struct {
|
||||||
Listeners []net.Listener
|
Listeners []net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSHBackendData struct {
|
|
||||||
IP string `json:"ip" validate:"required"`
|
|
||||||
Port uint16 `json:"port" validate:"required"`
|
|
||||||
Username string `json:"username" validate:"required"`
|
|
||||||
PrivateKey string `json:"privateKey" validate:"required"`
|
|
||||||
DisablePIDCheck bool `json:"disablePIDCheck"`
|
|
||||||
ListenOnIPs []string `json:"listenOnIPs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSHBackend struct {
|
type SSHBackend struct {
|
||||||
config *SSHBackendData
|
config *SSHBackendData
|
||||||
conn *ssh.Client
|
conn *ssh.Client
|
||||||
clients []*commonbackend.ProxyClientConnection
|
clients []*commonbackend.ProxyClientConnection
|
||||||
proxies []*SSHListener
|
proxies []*SSHListener
|
||||||
arrayPropMutex sync.Mutex
|
arrayPropMutex sync.Mutex
|
||||||
pid int
|
}
|
||||||
isReady bool
|
|
||||||
inReinitLoop bool
|
type SSHBackendData struct {
|
||||||
|
IP string `json:"ip" validate:"required"`
|
||||||
|
Port uint16 `json:"port" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
PrivateKey string `json:"privateKey" validate:"required"`
|
||||||
|
ListenOnIPs []string `json:"listenOnIPs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backend *SSHBackend) StartBackend(bytes []byte) (bool, error) {
|
func (backend *SSHBackend) StartBackend(bytes []byte) (bool, error) {
|
||||||
log.Info("SSHBackend is initializing...")
|
log.Info("SSHBackend is initializing...")
|
||||||
|
|
||||||
if validatorInstance == nil {
|
|
||||||
validatorInstance = validator.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
if backend.inReinitLoop {
|
|
||||||
for !backend.isReady {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var backendData SSHBackendData
|
var backendData SSHBackendData
|
||||||
|
|
||||||
if err := json.Unmarshal(bytes, &backendData); err != nil {
|
if err := json.Unmarshal(bytes, &backendData); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validatorInstance.Struct(&backendData); err != nil {
|
if err := validator.New().Struct(&backendData); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,71 +74,15 @@ func (backend *SSHBackend) StartBackend(bytes []byte) (bool, error) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", backendData.IP, backendData.Port)
|
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", backendData.IP, backendData.Port), config)
|
||||||
timeout := time.Duration(10 * time.Second)
|
|
||||||
|
|
||||||
rawTCPConn, err := net.DialTimeout("tcp", addr, timeout)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
connWithTimeout := &ConnWithTimeout{
|
backend.conn = conn
|
||||||
Conn: rawTCPConn,
|
|
||||||
ReadTimeout: timeout,
|
|
||||||
WriteTimeout: timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
c, chans, reqs, err := ssh.NewClientConn(connWithTimeout, addr, config)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := ssh.NewClient(c, chans, reqs)
|
|
||||||
backend.conn = client
|
|
||||||
|
|
||||||
if !backendData.DisablePIDCheck {
|
|
||||||
if backend.pid != 0 {
|
|
||||||
session, err := client.NewSession()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.Run(fmt.Sprintf("kill -9 %d", backend.pid))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to kill process: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := client.NewSession()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the parent PID of the shell so we can kill it if we disconnect
|
|
||||||
output, err := session.Output("ps --no-headers -fp $$ | awk '{print $3}'")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip the new line and convert to int
|
|
||||||
backend.pid, err = strconv.Atoi(string(output)[:len(output)-1])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go backend.backendDisconnectHandler()
|
|
||||||
go backend.backendKeepaliveHandler()
|
|
||||||
|
|
||||||
log.Info("SSHBackend has initialized successfully.")
|
log.Info("SSHBackend has initialized successfully.")
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,10 +96,6 @@ func (backend *SSHBackend) StopBackend() (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backend *SSHBackend) GetBackendStatus() (bool, error) {
|
|
||||||
return backend.conn != nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHBackend) StartProxy(command *commonbackend.AddProxy) (bool, error) {
|
func (backend *SSHBackend) StartProxy(command *commonbackend.AddProxy) (bool, error) {
|
||||||
listenerObject := &SSHListener{
|
listenerObject := &SSHListener{
|
||||||
SourceIP: command.SourceIP,
|
SourceIP: command.SourceIP,
|
||||||
|
@ -240,11 +134,6 @@ func (backend *SSHBackend) StartProxy(command *commonbackend.AddProxy) (bool, er
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to accept listener connection: %s", err.Error())
|
log.Warnf("failed to accept listener connection: %s", err.Error())
|
||||||
|
|
||||||
if err.Error() == "EOF" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,7 +190,8 @@ func (backend *SSHBackend) StartProxy(command *commonbackend.AddProxy) (bool, er
|
||||||
// Splice out the clientInstance by clientIndex
|
// Splice out the clientInstance by clientIndex
|
||||||
|
|
||||||
// TODO: change approach. It works but it's a bit wonky imho
|
// TODO: change approach. It works but it's a bit wonky imho
|
||||||
backend.clients = slices.Delete(backend.clients, clientIndex, clientIndex+1)
|
// I asked AI to do this as it's a relatively simple task and I forgot how to do this effectively
|
||||||
|
backend.clients = append(backend.clients[:clientIndex], backend.clients[clientIndex+1:]...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,20 +207,14 @@ func (backend *SSHBackend) StartProxy(command *commonbackend.AddProxy) (bool, er
|
||||||
|
|
||||||
for {
|
for {
|
||||||
len, err := forwardedConn.Read(forwardedBuffer)
|
len, err := forwardedConn.Read(forwardedBuffer)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != "EOF" && !errors.Is(err, net.ErrClosed) {
|
|
||||||
log.Errorf("failed to read from forwarded connection: %s", err.Error())
|
log.Errorf("failed to read from forwarded connection: %s", err.Error())
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = sourceConn.Write(forwardedBuffer[:len]); err != nil {
|
_, err = sourceConn.Write(forwardedBuffer[:len])
|
||||||
if err.Error() != "EOF" && !errors.Is(err, net.ErrClosed) {
|
if err != nil {
|
||||||
log.Errorf("failed to write to source connection: %s", err.Error())
|
log.Errorf("failed to write to source connection: %s", err.Error())
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,20 +225,14 @@ func (backend *SSHBackend) StartProxy(command *commonbackend.AddProxy) (bool, er
|
||||||
|
|
||||||
for {
|
for {
|
||||||
len, err := sourceConn.Read(sourceBuffer)
|
len, err := sourceConn.Read(sourceBuffer)
|
||||||
|
if err != nil && err.Error() != "EOF" && strings.HasSuffix(err.Error(), "use of closed network connection") {
|
||||||
if err != nil {
|
|
||||||
if err.Error() != "EOF" && !errors.Is(err, net.ErrClosed) {
|
|
||||||
log.Errorf("failed to read from source connection: %s", err.Error())
|
log.Errorf("failed to read from source connection: %s", err.Error())
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = forwardedConn.Write(sourceBuffer[:len]); err != nil {
|
_, err = forwardedConn.Write(sourceBuffer[:len])
|
||||||
if err.Error() != "EOF" && !errors.Is(err, net.ErrClosed) {
|
if err != nil && err.Error() != "EOF" && strings.HasSuffix(err.Error(), "use of closed network connection") {
|
||||||
log.Errorf("failed to write to forwarded connection: %s", err.Error())
|
log.Errorf("failed to write to forwarded connection: %s", err.Error())
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,7 +253,10 @@ func (backend *SSHBackend) StopProxy(command *commonbackend.RemoveProxy) (bool,
|
||||||
backend.arrayPropMutex.Lock()
|
backend.arrayPropMutex.Lock()
|
||||||
|
|
||||||
for proxyIndex, proxy := range backend.proxies {
|
for proxyIndex, proxy := range backend.proxies {
|
||||||
|
// Check if memory addresses are equal for the pointer
|
||||||
if command.SourceIP == proxy.SourceIP && command.SourcePort == proxy.SourcePort && command.DestPort == proxy.DestPort && command.Protocol == proxy.Protocol {
|
if command.SourceIP == proxy.SourceIP && command.SourcePort == proxy.SourcePort && command.DestPort == proxy.DestPort && command.Protocol == proxy.Protocol {
|
||||||
|
log.Debug("found proxy in StopProxy. shutting down listeners")
|
||||||
|
|
||||||
for _, listener := range proxy.Listeners {
|
for _, listener := range proxy.Listeners {
|
||||||
err := listener.Close()
|
err := listener.Close()
|
||||||
|
|
||||||
|
@ -385,8 +266,10 @@ func (backend *SSHBackend) StopProxy(command *commonbackend.RemoveProxy) (bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splice out the proxy instance by proxyIndex
|
// Splice out the proxy instance by proxyIndex
|
||||||
|
|
||||||
// TODO: change approach. It works but it's a bit wonky imho
|
// TODO: change approach. It works but it's a bit wonky imho
|
||||||
backend.proxies = slices.Delete(backend.proxies, proxyIndex, proxyIndex+1)
|
// I asked AI to do this as it's a relatively simple task and I forgot how to do this effectively
|
||||||
|
backend.proxies = append(backend.proxies[:proxyIndex], backend.proxies[proxyIndex+1:]...)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -417,10 +300,6 @@ func (backend *SSHBackend) CheckParametersForConnections(clientParameters *commo
|
||||||
func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
|
func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
|
||||||
var backendData SSHBackendData
|
var backendData SSHBackendData
|
||||||
|
|
||||||
if validatorInstance == nil {
|
|
||||||
validatorInstance = validator.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(arguments, &backendData); err != nil {
|
if err := json.Unmarshal(arguments, &backendData); err != nil {
|
||||||
return &commonbackend.CheckParametersResponse{
|
return &commonbackend.CheckParametersResponse{
|
||||||
IsValid: false,
|
IsValid: false,
|
||||||
|
@ -428,7 +307,7 @@ func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonba
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validatorInstance.Struct(&backendData); err != nil {
|
if err := validator.New().Struct(&backendData); err != nil {
|
||||||
return &commonbackend.CheckParametersResponse{
|
return &commonbackend.CheckParametersResponse{
|
||||||
IsValid: false,
|
IsValid: false,
|
||||||
Message: fmt.Sprintf("failed validation of parameters: %s", err.Error()),
|
Message: fmt.Sprintf("failed validation of parameters: %s", err.Error()),
|
||||||
|
@ -440,152 +319,6 @@ func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonba
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backend *SSHBackend) backendKeepaliveHandler() {
|
|
||||||
for {
|
|
||||||
if backend.conn != nil {
|
|
||||||
_, _, err := backend.conn.SendRequest("keepalive@openssh.com", true, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Keepalive message failed!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (backend *SSHBackend) backendDisconnectHandler() {
|
|
||||||
for {
|
|
||||||
if backend.conn != nil {
|
|
||||||
backend.conn.Wait()
|
|
||||||
backend.conn.Close()
|
|
||||||
|
|
||||||
backend.isReady = false
|
|
||||||
backend.inReinitLoop = true
|
|
||||||
|
|
||||||
log.Info("Disconnected from the remote SSH server. Attempting to reconnect in 5 seconds...")
|
|
||||||
} else {
|
|
||||||
log.Info("Retrying reconnection in 5 seconds...")
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
|
|
||||||
// Make the connection nil to accurately report our status incase GetBackendStatus is called
|
|
||||||
backend.conn = nil
|
|
||||||
|
|
||||||
// Use the last half of the code from the main initialization
|
|
||||||
signer, err := ssh.ParsePrivateKey([]byte(backend.config.PrivateKey))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to parse private key: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auth := ssh.PublicKeys(signer)
|
|
||||||
|
|
||||||
config := &ssh.ClientConfig{
|
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
||||||
User: backend.config.Username,
|
|
||||||
Auth: []ssh.AuthMethod{
|
|
||||||
auth,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", backend.config.IP, backend.config.Port)
|
|
||||||
timeout := time.Duration(10 * time.Second)
|
|
||||||
|
|
||||||
rawTCPConn, err := net.DialTimeout("tcp", addr, timeout)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to establish connection to the server: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
connWithTimeout := &ConnWithTimeout{
|
|
||||||
Conn: rawTCPConn,
|
|
||||||
ReadTimeout: timeout,
|
|
||||||
WriteTimeout: timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
c, chans, reqs, err := ssh.NewClientConn(connWithTimeout, addr, config)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to create SSH client connection: %s", err.Error())
|
|
||||||
rawTCPConn.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
client := ssh.NewClient(c, chans, reqs)
|
|
||||||
backend.conn = client
|
|
||||||
|
|
||||||
if !backend.config.DisablePIDCheck {
|
|
||||||
if backend.pid != 0 {
|
|
||||||
session, err := client.NewSession()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to create SSH command session: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.Run(fmt.Sprintf("kill -9 %d", backend.pid))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to kill process: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := client.NewSession()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to create SSH command session: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the parent PID of the shell so we can kill it if we disconnect
|
|
||||||
output, err := session.Output("ps --no-headers -fp $$ | awk '{print $3}'")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to execute command to fetch PID: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip the new line and convert to int
|
|
||||||
backend.pid, err = strconv.Atoi(string(output)[:len(output)-1])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to parse PID: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go backend.backendKeepaliveHandler()
|
|
||||||
|
|
||||||
log.Info("SSHBackend has reconnected successfully. Attempting to set up proxies again...")
|
|
||||||
|
|
||||||
for _, proxy := range backend.proxies {
|
|
||||||
ok, err := backend.StartProxy(&commonbackend.AddProxy{
|
|
||||||
SourceIP: proxy.SourceIP,
|
|
||||||
SourcePort: proxy.SourcePort,
|
|
||||||
DestPort: proxy.DestPort,
|
|
||||||
Protocol: proxy.Protocol,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to set up proxy: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Errorf("Failed to set up proxy: OK status is false")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("SSHBackend has reinitialized and restored state successfully.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,27 @@ services:
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@nextnet-postgres:5432/${POSTGRES_DB}?schema=nextnet
|
DATABASE_URL: postgresql://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@nextnet-postgres:5432/${POSTGRES_DB}?schema=nextnet
|
||||||
HERMES_POSTGRES_DSN: postgres://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@nextnet-postgres:5432/${POSTGRES_DB}
|
HERMES_POSTGRES_DSN: postgres://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@nextnet-postgres:5432/${POSTGRES_DB}
|
||||||
HERMES_JWT_SECRET: ${JWT_SECRET}
|
|
||||||
HERMES_DATABASE_BACKEND: postgresql
|
HERMES_DATABASE_BACKEND: postgresql
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
|
|
||||||
|
# WARN: The LOM is deprecated and likely broken currently.
|
||||||
|
#
|
||||||
|
# NOTE: For this to work correctly, the nextnet-api must be version > 0.1.1
|
||||||
|
# or have a version with backported username support, incl. logins
|
||||||
|
lom:
|
||||||
|
image: ghcr.io/imterah/hermes-lom:latest
|
||||||
|
container_name: hermes-lom
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 2222:2222
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
volumes:
|
||||||
|
- ssh_key_data:/app/keys
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:17.2
|
image: postgres:17.2
|
||||||
container_name: nextnet-postgres
|
container_name: nextnet-postgres
|
||||||
|
@ -22,6 +37,7 @@ services:
|
||||||
POSTGRES_USER: ${POSTGRES_USERNAME}
|
POSTGRES_USER: ${POSTGRES_USERNAME}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
ssh_key_data:
|
ssh_key_data:
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
# Profiling
|
|
||||||
To profile any backend code based on `backendutil`, follow these steps:
|
|
||||||
1. Rebuild the backend with the `debug` flag: `cd $BACKEND_HERE; GOOS=linux go build -tags debug .; cd ..`
|
|
||||||
2. Copy the binary to the target machine (if applicable), and stop the API server.
|
|
||||||
3. If you want to profile the CPU utilization, write `cpu` to the file `/tmp/hermes.backendlauncher.profilebackends`: `echo -n "cpu" > /tmp/hermes.backendlauncher.profilebackends`. Else, replace `cpu` with `mem`.
|
|
||||||
4. Start the API server, with development mode and debug logging enabled.
|
|
|
@ -1,115 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/apiclient"
|
|
||||||
"git.terah.dev/imterah/hermes/frontend/config"
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/term"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateUserCommand(cCtx *cli.Context) error {
|
|
||||||
configPath := cCtx.String("config-path")
|
|
||||||
|
|
||||||
var configContents *config.Config
|
|
||||||
|
|
||||||
_, err := os.Stat(configPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
configContents = &config.Config{}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("failed to get configuration file information: %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
configContents, err = config.ReadAndParseConfig(configPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read and parse configuration file: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
username := cCtx.String("username")
|
|
||||||
|
|
||||||
if username == "" {
|
|
||||||
if configContents.Username == "" {
|
|
||||||
return fmt.Errorf("username not specified and username is not in the configuration file")
|
|
||||||
}
|
|
||||||
|
|
||||||
username = configContents.Username
|
|
||||||
}
|
|
||||||
|
|
||||||
var password string
|
|
||||||
|
|
||||||
if cCtx.Bool("ask-password") {
|
|
||||||
fmt.Print("Password: ")
|
|
||||||
passwordBytes, err := term.ReadPassword(int(syscall.Stdin))
|
|
||||||
fmt.Print("\n")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read password from console: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
password = string(passwordBytes)
|
|
||||||
} else {
|
|
||||||
password = cCtx.String("password")
|
|
||||||
|
|
||||||
if password == "" {
|
|
||||||
return fmt.Errorf("password is not specified and password asking is not enabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var serverURL string
|
|
||||||
|
|
||||||
if cCtx.String("server-url") == "" {
|
|
||||||
if configContents.APIPath == "" {
|
|
||||||
return fmt.Errorf("server URL not specified and server URL is not in the configuration file")
|
|
||||||
}
|
|
||||||
|
|
||||||
serverURL = configContents.APIPath
|
|
||||||
} else {
|
|
||||||
serverURL = cCtx.String("server-url")
|
|
||||||
}
|
|
||||||
|
|
||||||
fullName := cCtx.String("full-name")
|
|
||||||
email := cCtx.String("email")
|
|
||||||
isBot := cCtx.Bool("user-is-bot")
|
|
||||||
|
|
||||||
log.Info("Creating user...")
|
|
||||||
|
|
||||||
api := &apiclient.HermesAPIClient{
|
|
||||||
URL: serverURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken, err := api.UserCreate(fullName, username, email, password, isBot)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create user: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Successfully created user.")
|
|
||||||
|
|
||||||
if cCtx.Bool("do-not-save-configuration") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
configContents.Username = username
|
|
||||||
configContents.RefreshToken = refreshToken
|
|
||||||
configContents.APIPath = serverURL
|
|
||||||
|
|
||||||
data, err := yaml.Marshal(configContents)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to marshal configuration data: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
os.WriteFile(configPath, data, 0644)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/apiclient"
|
|
||||||
"git.terah.dev/imterah/hermes/frontend/config"
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/term"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetRefreshTokenCommand(cCtx *cli.Context) error {
|
|
||||||
configPath := cCtx.String("config-path")
|
|
||||||
|
|
||||||
var configContents *config.Config
|
|
||||||
|
|
||||||
_, err := os.Stat(configPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
configContents = &config.Config{}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("failed to get configuration file information: %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
configContents, err = config.ReadAndParseConfig(configPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read and parse configuration file: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var username string
|
|
||||||
var password string
|
|
||||||
|
|
||||||
if cCtx.String("username") == "" {
|
|
||||||
if configContents.Username == "" {
|
|
||||||
return fmt.Errorf("username not specified and username is not in the configuration file")
|
|
||||||
}
|
|
||||||
|
|
||||||
username = configContents.Username
|
|
||||||
} else {
|
|
||||||
username = cCtx.String("username")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cCtx.Bool("ask-password") {
|
|
||||||
fmt.Print("Password: ")
|
|
||||||
passwordBytes, err := term.ReadPassword(int(syscall.Stdin))
|
|
||||||
fmt.Print("\n")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read password from console: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
password = string(passwordBytes)
|
|
||||||
} else {
|
|
||||||
password = cCtx.String("password")
|
|
||||||
|
|
||||||
if password == "" {
|
|
||||||
return fmt.Errorf("password is not specified and password asking is not enabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serverURL := cCtx.String("server-url")
|
|
||||||
log.Info("Authenticating with API...")
|
|
||||||
|
|
||||||
api := &apiclient.HermesAPIClient{
|
|
||||||
URL: serverURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken, err := api.UserGetRefreshToken(&username, nil, password)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to authenticate with the API: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
configContents.Username = username
|
|
||||||
configContents.RefreshToken = refreshToken
|
|
||||||
configContents.APIPath = serverURL
|
|
||||||
|
|
||||||
log.Info("Writing configuration file...")
|
|
||||||
data, err := yaml.Marshal(configContents)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to marshal configuration data: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("config path: %s", configPath)
|
|
||||||
|
|
||||||
os.WriteFile(configPath, data, 0644)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
RefreshToken string `json:"token"`
|
|
||||||
APIPath string `json:"api_path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadAndParseConfig(configFile string) (*Config, error) {
|
|
||||||
configFileContents, err := os.ReadFile(configFile)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &Config{}
|
|
||||||
err = yaml.Unmarshal(configFileContents, config)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
HERMES_LOG_LEVEL=debug
|
|
143
frontend/main.go
143
frontend/main.go
|
@ -1,143 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/frontend/commands/users"
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
|
||||||
|
|
||||||
if logLevel != "" {
|
|
||||||
switch logLevel {
|
|
||||||
case "debug":
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
|
|
||||||
case "info":
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
|
|
||||||
case "warn":
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
|
|
||||||
case "error":
|
|
||||||
log.SetLevel(log.ErrorLevel)
|
|
||||||
|
|
||||||
case "fatal":
|
|
||||||
log.SetLevel(log.FatalLevel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configDir, err := os.UserConfigDir()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to get configuration directory: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
app := &cli.App{
|
|
||||||
Name: "hermcli",
|
|
||||||
Usage: "client for Hermes -- port forwarding across boundaries",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "config-path",
|
|
||||||
Aliases: []string{"config", "cp", "c"},
|
|
||||||
Value: path.Join(configDir, "hermcli.yml"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Commands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "login",
|
|
||||||
Usage: "log in to the API",
|
|
||||||
Action: users.GetRefreshTokenCommand,
|
|
||||||
Aliases: []string{"l"},
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "username",
|
|
||||||
Aliases: []string{"user", "u"},
|
|
||||||
Usage: "username to authenticate as",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "password",
|
|
||||||
Aliases: []string{"pass", "p"},
|
|
||||||
Usage: "password to authenticate with",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "server-url",
|
|
||||||
Aliases: []string{"server", "s"},
|
|
||||||
Usage: "URL of the server to authenticate with",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "ask-password",
|
|
||||||
Aliases: []string{"ask-pass", "ap"},
|
|
||||||
Usage: "asks you the password to authenticate with",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "users",
|
|
||||||
Usage: "user management commands",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "create",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "create a user",
|
|
||||||
Action: users.CreateUserCommand,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "full-name",
|
|
||||||
Aliases: []string{"name", "n"},
|
|
||||||
Usage: "full name for the user",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "username",
|
|
||||||
Aliases: []string{"user", "us"},
|
|
||||||
Usage: "username to give the user",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "email",
|
|
||||||
Aliases: []string{"e"},
|
|
||||||
Usage: "email to give the user",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "password",
|
|
||||||
Aliases: []string{"pass", "p"},
|
|
||||||
Usage: "password to give the user",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "server-url",
|
|
||||||
Aliases: []string{"server", "s"},
|
|
||||||
Usage: "URL of the server to connect with",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "ask-password",
|
|
||||||
Aliases: []string{"ask-pass", "ap"},
|
|
||||||
Usage: "asks you the password to give the user",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "user-is-bot",
|
|
||||||
Aliases: []string{"user-bot", "ub", "u"},
|
|
||||||
Usage: "if set, makes the user flagged as a bot",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "do-not-save-configuration",
|
|
||||||
Aliases: []string{"no-save", "ns"},
|
|
||||||
Usage: "doesn't save the authenticated user credentials",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
6
init.sh
6
init.sh
|
@ -7,11 +7,11 @@ if [ ! -d "backend/.tmp" ]; then
|
||||||
mkdir backend/.tmp
|
mkdir backend/.tmp
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "frontend/.env" ]; then
|
if [ ! -f "backend-legacy/.env" ]; then
|
||||||
cp frontend/dev.env frontend/.env
|
cp backend-legacy/dev.env backend-legacy/.env
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set -a
|
set -a
|
||||||
|
source backend-legacy/.env
|
||||||
source backend/.env
|
source backend/.env
|
||||||
source frontend/.env
|
|
||||||
set +a
|
set +a
|
||||||
|
|
46
migration-entrypoint.sh
Executable file
46
migration-entrypoint.sh
Executable file
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
echo "Welcome to the Hermes migration assistant."
|
||||||
|
|
||||||
|
if [ ! -f "/tmp/db.json.gz" ]; then
|
||||||
|
echo "Exporting database contents..."
|
||||||
|
cd /app/legacy
|
||||||
|
node out/tools/exportDBContents.js /tmp/db.json.gz
|
||||||
|
BACKUP_EXIT_CODE=$?
|
||||||
|
|
||||||
|
if [ $BACKUP_EXIT_CODE -ne 0 ]; then
|
||||||
|
echo "Failed to export database contents!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "!! IMPORTANT !!"
|
||||||
|
echo "Database backup contents below:"
|
||||||
|
echo "==== BEGIN BACKUP ===="
|
||||||
|
cat /tmp/db.json.gz | base64
|
||||||
|
echo "==== END BACKUP ===="
|
||||||
|
echo "When copying, do NOT copy the BEGIN and END sections."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Wiping old database..."
|
||||||
|
cat >> /tmp/wipe.sql << EOF
|
||||||
|
CREATE DATABASE temp;
|
||||||
|
\c temp
|
||||||
|
DROP DATABASE $HERMES_MIGRATE_POSTGRES_DATABASE;
|
||||||
|
CREATE DATABASE $HERMES_MIGRATE_POSTGRES_DATABASE;
|
||||||
|
\c nextnet
|
||||||
|
DROP DATABASE temp;
|
||||||
|
EOF
|
||||||
|
psql "$HERMES_POSTGRES_DSN" < /tmp/wipe.sql
|
||||||
|
rm -rf /tmp/wipe.sql
|
||||||
|
echo "Restoring backup..."
|
||||||
|
|
||||||
|
cd /app/modern
|
||||||
|
./hermes -b ./backends.json import --bp /tmp/db.json.gz
|
||||||
|
|
||||||
|
echo "Restored backup. If this restore fails after the database has wiped, get a shell into the container,"
|
||||||
|
echo "copy the backup contents into the container (base64 decoded) at '/tmp/db.json.gz',"
|
||||||
|
echo "and rerun /app/entrypoint.sh."
|
||||||
|
echo ""
|
||||||
|
echo "If further issues continue, open an issue at 'https://git.terah.dev/imterah/hermes'."
|
||||||
|
echo "If the migration succeeded, congratulations!"
|
||||||
|
|
||||||
|
sleep 10000
|
|
@ -1,4 +1,5 @@
|
||||||
|
# These are default values, please change these!
|
||||||
|
|
||||||
POSTGRES_USERNAME=hermes
|
POSTGRES_USERNAME=hermes
|
||||||
POSTGRES_PASSWORD=hermes
|
POSTGRES_PASSWORD=hermes
|
||||||
POSTGRES_DB=hermes
|
POSTGRES_DB=hermes
|
||||||
JWT_SECRET=hermes
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ post {
|
||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiMSJdLCJleHAiOjE3MzYyMzI2NjEsIm5iZiI6MTczNjE0NjI2MSwiaWF0IjoxNzM2MTQ2MjYxfQ.juoZ74xs-FBnbbT9Zlei1LmcNx7kTEfzymHlVbeMmtQ",
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiMSJdLCJleHAiOjE3MzUwNzY0MTEsIm5iZiI6MTczNDk5MDAxMSwiaWF0IjoxNzM0OTkwMDExfQ.N9TLraX4peHt7FKv8tPcHuEzL0K7T2IBEw3piQS_4OY",
|
||||||
"name": "SSH",
|
"name": "SSH",
|
||||||
"id": 1
|
"id": 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,18 @@
|
||||||
}: pkgs.mkShell {
|
}: pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
# api/
|
# api/
|
||||||
|
nodejs
|
||||||
|
openssl
|
||||||
|
lsof
|
||||||
go
|
go
|
||||||
gopls
|
gopls
|
||||||
];
|
];
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
if [ -f init.sh ]; then
|
export PRISMA_QUERY_ENGINE_BINARY=${pkgs.prisma-engines}/bin/query-engine
|
||||||
|
export PRISMA_QUERY_ENGINE_LIBRARY=${pkgs.prisma-engines}/lib/libquery_engine.node
|
||||||
|
export PRISMA_SCHEMA_ENGINE_BINARY=${pkgs.prisma-engines}/bin/schema-engine
|
||||||
|
|
||||||
source init.sh
|
source init.sh
|
||||||
fi
|
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|
133
sshfrontend/.gitignore
vendored
Normal file
133
sshfrontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
# Output
|
||||||
|
out
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
14
sshfrontend/Dockerfile
Normal file
14
sshfrontend/Dockerfile
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
FROM node:22.11.0-bookworm
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/greysoh/nextnet"
|
||||||
|
WORKDIR /app/
|
||||||
|
COPY src /app/src
|
||||||
|
COPY tsconfig.json /app/
|
||||||
|
COPY package.json /app/
|
||||||
|
COPY package-lock.json /app/
|
||||||
|
COPY docker-entrypoint.sh /app/
|
||||||
|
RUN npm install --save-dev
|
||||||
|
RUN npm run build
|
||||||
|
RUN rm out/**/*.ts out/**/*.map
|
||||||
|
RUN rm -rf src
|
||||||
|
RUN npm prune --production
|
||||||
|
ENTRYPOINT sh docker-entrypoint.sh
|
28
sshfrontend/LICENSE
Normal file
28
sshfrontend/LICENSE
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2024, Greyson
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2
sshfrontend/README.md
Normal file
2
sshfrontend/README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# NextNet LOM
|
||||||
|
Lights Out Management, NextNet style
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue