Compare commits
11 Commits
v0.1.0-alp
...
v0.1.1-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 43827d0d9e | |||
| 1d60ba2999 | |||
| 268955732a | |||
| 1083b54fb9 | |||
| 45baaf8db8 | |||
| fbff33d201 | |||
| e994f13526 | |||
| 775186038e | |||
| 6cbcb272d0 | |||
| 91e4758bb8 | |||
| 7a448034e4 |
@@ -10,8 +10,8 @@
|
||||
|
||||
# --- Application ---
|
||||
KEYWARDEN_PORT=8080
|
||||
KEYWARDEN_ADMIN_USER=admin
|
||||
KEYWARDEN_ADMIN_EMAIL=admin@keywarden.local
|
||||
KEYWARDEN_OWNER_USER=admin
|
||||
KEYWARDEN_OWNER_EMAIL=admin@keywarden.local
|
||||
KEYWARDEN_SESSION_KEY=change-me-to-a-random-string
|
||||
KEYWARDEN_ENCRYPTION_KEY=change-me-encryption-key-32chars
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install build dependencies
|
||||
run: apk add --no-cache gcc musl-dev sqlite-dev git
|
||||
run: apk add --no-cache gcc musl-dev sqlite-dev git nodejs
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -6,6 +6,7 @@ name: Release Docker Image
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
IMAGE_NAME: keywarden
|
||||
@@ -18,16 +19,30 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: |
|
||||
# Release tag is e.g. v0.1.0
|
||||
# Release tag from release event, or latest git tag for workflow_dispatch
|
||||
TAG="${{ github.event.release.tag_name }}"
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="$(git describe --tags --abbrev=0 2>/dev/null || echo '')"
|
||||
fi
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "::error::No tag found. Please create a release or tag first."
|
||||
exit 1
|
||||
fi
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
# Strip 'v' prefix for docker tag if needed
|
||||
VERSION="${TAG#v}"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
# Strip protocol (https://) from REGISTRY_URL for Docker tags
|
||||
REGISTRY="${{ vars.REGISTRY_URL }}"
|
||||
REGISTRY="${REGISTRY#https://}"
|
||||
REGISTRY="${REGISTRY#http://}"
|
||||
echo "registry=${REGISTRY}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v3
|
||||
@@ -45,8 +60,8 @@ jobs:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ vars.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ vars.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
||||
${{ steps.version.outputs.registry }}/${{ secrets.REGISTRY_USER }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ steps.version.outputs.registry }}/${{ secrets.REGISTRY_USER }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=Keywarden
|
||||
org.opencontainers.image.description=Centralized SSH Key Management and Deployment
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: apk add --no-cache git gcc musl-dev sqlite-dev
|
||||
run: apk add --no-cache git gcc musl-dev sqlite-dev nodejs
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,6 +21,9 @@ vendor/
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# AI workspace
|
||||
.ki-workspace/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
42
README.md
42
README.md
@@ -10,7 +10,7 @@
|
||||
>
|
||||
> - **Do NOT expose this application directly to the public internet.** Use it only in trusted, private networks.
|
||||
> - The software may contain bugs, incomplete features, or security issues.
|
||||
> - **Your feedback is valuable!** If you discover bugs or have suggestions, please report them at [git.techniverse.net/scriptos/keywarden](https://git.techniverse.net/scriptos/keywarden). Every report helps improve the project.
|
||||
> - **Your feedback is valuable!** If you discover bugs or have suggestions, please open an [Issue on GitHub](https://github.com/pscriptos/keywarden/issues). Every report helps improve the project.
|
||||
|
||||
---
|
||||
|
||||
@@ -44,14 +44,19 @@ git clone https://git.techniverse.net/scriptos/keywarden.git
|
||||
cd keywarden
|
||||
```
|
||||
|
||||
Create a `.env` file:
|
||||
Create a `.env` file and generate two separate cryptographically secure keys:
|
||||
|
||||
```env
|
||||
KEYWARDEN_SESSION_KEY=your-random-session-key-at-least-32-characters
|
||||
KEYWARDEN_ENCRYPTION_KEY=your-random-encryption-key-at-least-32-chars
|
||||
```bash
|
||||
# Generate keys (run twice, once per key):
|
||||
openssl rand -base64 48
|
||||
```
|
||||
|
||||
> **Important:** Change both keys to unique random strings. The encryption key protects all stored SSH private keys — if lost, they cannot be recovered.
|
||||
```env
|
||||
KEYWARDEN_SESSION_KEY=<first generated string>
|
||||
KEYWARDEN_ENCRYPTION_KEY=<second generated string>
|
||||
```
|
||||
|
||||
> **Important:** Change both keys to unique random strings. The encryption key protects all stored SSH private keys — if lost, they cannot be recovered. See the [Quick Start Guide](docs/quickstart.md) for more options to generate secure keys.
|
||||
|
||||
### 2. Start
|
||||
|
||||
@@ -113,4 +118,27 @@ For detailed documentation, see the [docs/](docs/README.md) folder:
|
||||
|
||||
Keywarden is licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0-or-later)](LICENSE).
|
||||
|
||||
© 2026 Patrick Asmus ([scriptos](https://git.techniverse.net/scriptos))
|
||||
© 2026 Patrick Asmus ([scriptos](https://git.techniverse.net/scriptos))
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
Join the **Keywarden Matrix chat** to discuss the project, ask questions, or share feedback:
|
||||
|
||||
[](https://matrix.to/#/#keywarden:techniverse.net)
|
||||
|
||||
➡️ [#keywarden:techniverse.net](https://matrix.to/#/#keywarden:techniverse.net)
|
||||
|
||||
---
|
||||
|
||||
## Repository & Mirror
|
||||
|
||||
| | URL |
|
||||
|---|---|
|
||||
| **Primary (Gitea)** | [git.techniverse.net/scriptos/keywarden](https://git.techniverse.net/scriptos/keywarden) |
|
||||
| **Mirror (GitHub)** | [github.com/pscriptos/keywarden](https://github.com/pscriptos/keywarden) |
|
||||
|
||||
The **primary repository** is hosted on Gitea. The GitHub repository is a read-only mirror.
|
||||
|
||||
**Bug reports & feature requests:** Please open an [Issue on GitHub](https://github.com/pscriptos/keywarden/issues) — registration on the Gitea instance is currently closed.
|
||||
@@ -60,17 +60,18 @@ func main() {
|
||||
mailSvc := mail.NewService(cfg)
|
||||
|
||||
// Create default owner if no users exist (password is auto-generated)
|
||||
adminUser := getEnv("KEYWARDEN_ADMIN_USER", "admin")
|
||||
adminEmail := getEnv("KEYWARDEN_ADMIN_EMAIL", "admin@keywarden.local")
|
||||
// Support legacy KEYWARDEN_ADMIN_USER / KEYWARDEN_ADMIN_EMAIL for existing installations
|
||||
ownerUser := getEnvWithLegacy("KEYWARDEN_OWNER_USER", "KEYWARDEN_ADMIN_USER", "admin")
|
||||
ownerEmail := getEnvWithLegacy("KEYWARDEN_OWNER_EMAIL", "KEYWARDEN_ADMIN_EMAIL", "admin@keywarden.local")
|
||||
|
||||
created, generatedPass, err := authSvc.EnsureAdmin(adminUser, adminEmail)
|
||||
created, generatedPass, err := authSvc.EnsureAdmin(ownerUser, ownerEmail)
|
||||
if err != nil {
|
||||
logging.Fatal("Failed to create admin user: %v", err)
|
||||
logging.Fatal("Failed to create owner user: %v", err)
|
||||
}
|
||||
if created {
|
||||
logging.Info("════════════════════════════════════════════════════════════")
|
||||
logging.Info(" Initial owner account created")
|
||||
logging.Info(" Username: %s", adminUser)
|
||||
logging.Info(" Username: %s", ownerUser)
|
||||
logging.Info(" Password: %s", generatedPass)
|
||||
logging.Info(" Please change this password after first login!")
|
||||
logging.Info("════════════════════════════════════════════════════════════")
|
||||
@@ -137,3 +138,17 @@ func getEnv(key, fallback string) string {
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// getEnvWithLegacy checks the primary key first, then falls back to the
|
||||
// legacy (deprecated) key, and finally to the default value. This ensures
|
||||
// existing installations that still use the old variable name keep working.
|
||||
func getEnvWithLegacy(primary, legacy, fallback string) string {
|
||||
if val, ok := os.LookupEnv(primary); ok {
|
||||
return val
|
||||
}
|
||||
if val, ok := os.LookupEnv(legacy); ok {
|
||||
logging.Warn("Environment variable %s is deprecated, please rename to %s", legacy, primary)
|
||||
return val
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
services:
|
||||
keywarden:
|
||||
build: .
|
||||
image: git.techniverse.net/scriptos/keywarden:latest
|
||||
container_name: keywarden
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${KEYWARDEN_PORT:-8080}:${KEYWARDEN_PORT:-8080}"
|
||||
volumes:
|
||||
- keywarden_data:/data
|
||||
- ./data:/data
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
keywarden_net:
|
||||
ipv4_address: 172.23.64.10
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:${KEYWARDEN_PORT:-8080}/api/health"]
|
||||
interval: 30s
|
||||
@@ -16,6 +19,12 @@ services:
|
||||
start_period: 10s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
keywarden_data:
|
||||
driver: local
|
||||
networks:
|
||||
keywarden_net:
|
||||
name: keywarden.dockernetwork.local
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.64.0/24
|
||||
gateway: 172.23.64.1
|
||||
ip_range: 172.23.64.128/25
|
||||
|
||||
@@ -44,6 +44,14 @@ Keywarden provides a clean web UI to generate, import, and securely store SSH ke
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
Have questions, ideas, or feedback? Join the Keywarden Matrix chat room:
|
||||
|
||||
➡️ [#keywarden:techniverse.net](https://matrix.to/#/#keywarden:techniverse.net)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Keywarden is licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0-or-later)](../LICENSE).
|
||||
|
||||
@@ -82,12 +82,11 @@ In addition to the application-level backup, you can also back up the Docker vol
|
||||
# Stop the container
|
||||
docker compose down
|
||||
|
||||
# Backup the volume
|
||||
docker run --rm -v keywarden_keywarden_data:/data -v $(pwd):/backup \
|
||||
alpine tar czf /backup/keywarden-volume-backup.tar.gz /data
|
||||
# Backup the data directory
|
||||
tar czf keywarden-volume-backup.tar.gz ./data
|
||||
|
||||
# Start the container
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This captures the raw SQLite database file and all data files. Note that this backup is **not encrypted** — protect it accordingly.
|
||||
Since data is stored in the `./data` bind mount on the host, you can back it up directly without needing a helper container. Note that this backup is **not encrypted** — protect it accordingly.
|
||||
|
||||
@@ -144,6 +144,14 @@ Test files are co-located with their packages (e.g., `auth_test.go`, `config_tes
|
||||
- Error messages should be lowercase
|
||||
- Log messages use the structured logging package (`logging.Info`, `logging.Debug`, etc.)
|
||||
|
||||
## Community & Communication
|
||||
|
||||
For questions, discussions, and coordination with other contributors, join the Matrix chat:
|
||||
|
||||
➡️ [#keywarden:techniverse.net](https://matrix.to/#/#keywarden:techniverse.net)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
All contributions must be compatible with the [AGPL-3.0-or-later](../LICENSE) license.
|
||||
|
||||
@@ -34,15 +34,18 @@ A complete `docker-compose.yml`:
|
||||
```yaml
|
||||
services:
|
||||
keywarden:
|
||||
build: .
|
||||
image: git.techniverse.net/scriptos/keywarden:latest
|
||||
container_name: keywarden
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${KEYWARDEN_PORT:-8080}:${KEYWARDEN_PORT:-8080}"
|
||||
volumes:
|
||||
- keywarden_data:/data
|
||||
- ./data:/data
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
keywarden_net:
|
||||
ipv4_address: 172.23.64.10
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:${KEYWARDEN_PORT:-8080}/api/health"]
|
||||
interval: 30s
|
||||
@@ -50,9 +53,15 @@ services:
|
||||
start_period: 10s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
keywarden_data:
|
||||
driver: local
|
||||
networks:
|
||||
keywarden_net:
|
||||
name: keywarden.dockernetwork.local
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.64.0/24
|
||||
gateway: 172.23.64.1
|
||||
ip_range: 172.23.64.128/25
|
||||
```
|
||||
|
||||
### Environment File (.env)
|
||||
@@ -68,9 +77,9 @@ KEYWARDEN_ENCRYPTION_KEY=generate-another-random-string-32-chars
|
||||
KEYWARDEN_PORT=8080
|
||||
KEYWARDEN_LOG_LEVEL=INFO
|
||||
|
||||
# Initial admin (only used on first startup)
|
||||
KEYWARDEN_ADMIN_USER=admin
|
||||
KEYWARDEN_ADMIN_EMAIL=admin@example.com
|
||||
# Initial owner (only used on first startup)
|
||||
KEYWARDEN_OWNER_USER=admin
|
||||
KEYWARDEN_OWNER_EMAIL=admin@example.com
|
||||
|
||||
# HTTPS / Reverse Proxy
|
||||
KEYWARDEN_BASE_URL=https://keywarden.example.com
|
||||
@@ -180,12 +189,9 @@ The Docker HEALTHCHECK is configured automatically in the Dockerfile.
|
||||
To update Keywarden:
|
||||
|
||||
```bash
|
||||
# Pull latest changes (if building from source)
|
||||
git pull
|
||||
|
||||
# Rebuild and restart
|
||||
# Pull latest image and restart
|
||||
docker compose pull
|
||||
docker compose down
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
|
||||
@@ -25,14 +25,16 @@ Complete reference of all configuration options for Keywarden. All settings are
|
||||
| `KEYWARDEN_RATE_LIMIT_LOGIN` | `10` | Maximum login POST attempts per IP per minute. Set to `0` to disable. |
|
||||
| `KEYWARDEN_MAX_REQUEST_SIZE` | `10485760` | Maximum request body size in bytes (default: 10 MB). Set to `0` for no limit. |
|
||||
|
||||
## Initial Admin Account
|
||||
## Initial Owner Account
|
||||
|
||||
These variables are only used on first startup when no users exist in the database:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `KEYWARDEN_ADMIN_USER` | `admin` | Username for the initial owner account |
|
||||
| `KEYWARDEN_ADMIN_EMAIL` | `admin@keywarden.local` | Email for the initial owner account |
|
||||
| `KEYWARDEN_OWNER_USER` | `admin` | Username for the initial owner account |
|
||||
| `KEYWARDEN_OWNER_EMAIL` | `admin@keywarden.local` | Email for the initial owner account |
|
||||
|
||||
> **Note:** The previous variable names `KEYWARDEN_ADMIN_USER` and `KEYWARDEN_ADMIN_EMAIL` are still accepted for backward compatibility but are deprecated. Please update your `.env` file to use the new names.
|
||||
|
||||
The initial password is auto-generated (20 characters, alphanumeric) and printed to the startup log. It must be changed on first login.
|
||||
|
||||
@@ -74,9 +76,9 @@ KEYWARDEN_ENCRYPTION_KEY=mX9nP2qR4sT6uV8wY0zA1bC3dE5fG7hI
|
||||
KEYWARDEN_PORT=8080
|
||||
KEYWARDEN_LOG_LEVEL=INFO
|
||||
|
||||
# Initial admin (only used on first startup)
|
||||
KEYWARDEN_ADMIN_USER=admin
|
||||
KEYWARDEN_ADMIN_EMAIL=admin@example.com
|
||||
# Initial owner (only used on first startup)
|
||||
KEYWARDEN_OWNER_USER=admin
|
||||
KEYWARDEN_OWNER_EMAIL=admin@example.com
|
||||
|
||||
# Reverse proxy / HTTPS
|
||||
KEYWARDEN_BASE_URL=https://keywarden.example.com
|
||||
|
||||
@@ -17,14 +17,29 @@ mkdir keywarden && cd keywarden
|
||||
|
||||
Create a `.env` file with at minimum these settings:
|
||||
|
||||
Generate two separate, cryptographically secure random strings (minimum 32 characters each):
|
||||
|
||||
```bash
|
||||
# Linux / macOS
|
||||
openssl rand -base64 48
|
||||
|
||||
# Alternative without OpenSSL
|
||||
head -c 48 /dev/urandom | base64
|
||||
|
||||
# Windows (PowerShell)
|
||||
[Convert]::ToBase64String((1..48 | ForEach-Object { Get-Random -Max 256 }) -as [byte[]])
|
||||
```
|
||||
|
||||
Each command produces a 64-character Base64 string. Run it **twice** — once for each key — and paste the values below:
|
||||
|
||||
```env
|
||||
# REQUIRED: Change these for security!
|
||||
KEYWARDEN_SESSION_KEY=your-random-session-key-at-least-32-characters
|
||||
KEYWARDEN_ENCRYPTION_KEY=your-random-encryption-key-at-least-32-chars
|
||||
KEYWARDEN_SESSION_KEY=<first generated string>
|
||||
KEYWARDEN_ENCRYPTION_KEY=<second generated string>
|
||||
|
||||
# Optional: Admin credentials (defaults: admin / auto-generated password)
|
||||
KEYWARDEN_ADMIN_USER=admin
|
||||
KEYWARDEN_ADMIN_EMAIL=admin@example.com
|
||||
# Optional: Owner credentials (defaults: admin / auto-generated password)
|
||||
KEYWARDEN_OWNER_USER=admin
|
||||
KEYWARDEN_OWNER_EMAIL=admin@example.com
|
||||
|
||||
# Optional: Port (default: 8080)
|
||||
KEYWARDEN_PORT=8080
|
||||
@@ -43,33 +58,28 @@ services:
|
||||
ports:
|
||||
- "${KEYWARDEN_PORT:-8080}:${KEYWARDEN_PORT:-8080}"
|
||||
volumes:
|
||||
- keywarden_data:/data
|
||||
- ./data:/data
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
keywarden_net:
|
||||
ipv4_address: 172.23.64.10
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:${KEYWARDEN_PORT:-8080}/api/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 10s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
keywarden_data:
|
||||
driver: local
|
||||
```
|
||||
|
||||
Or, to build from source:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
keywarden:
|
||||
build: .
|
||||
container_name: keywarden
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${KEYWARDEN_PORT:-8080}:${KEYWARDEN_PORT:-8080}"
|
||||
volumes:
|
||||
- keywarden_data:/data
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
volumes:
|
||||
keywarden_data:
|
||||
driver: local
|
||||
networks:
|
||||
keywarden_net:
|
||||
name: keywarden.dockernetwork.local
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.64.0/24
|
||||
gateway: 172.23.64.1
|
||||
ip_range: 172.23.64.128/25
|
||||
```
|
||||
|
||||
## 4. Start Keywarden
|
||||
|
||||
@@ -213,3 +213,11 @@ Every HTTP request is logged with:
|
||||
- Response time
|
||||
- Client IP address
|
||||
- Username (if authenticated)
|
||||
|
||||
---
|
||||
|
||||
## Still Need Help?
|
||||
|
||||
If your issue isn't covered here, join the community Matrix chat to ask for help:
|
||||
|
||||
➡️ [#keywarden:techniverse.net](https://matrix.to/#/#keywarden:techniverse.net)
|
||||
|
||||
@@ -46,7 +46,7 @@ func (s *Service) DeployKey(key *models.SSHKey, server *models.Server, authPriva
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
addr := net.JoinHostPort(server.Hostname, fmt.Sprintf("%d", server.Port))
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
s.logDeployment(key.ID, server.ID, "failed", fmt.Sprintf("connection failed: %v", err))
|
||||
@@ -92,7 +92,7 @@ func (s *Service) DeployKeyWithPassword(key *models.SSHKey, server *models.Serve
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
addr := net.JoinHostPort(server.Hostname, fmt.Sprintf("%d", server.Port))
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
s.logDeployment(key.ID, server.ID, "failed", fmt.Sprintf("connection failed: %v", err))
|
||||
@@ -140,7 +140,7 @@ func (s *Service) RemoveKey(key *models.SSHKey, server *models.Server, authPriva
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
addr := net.JoinHostPort(server.Hostname, fmt.Sprintf("%d", server.Port))
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to server for key removal: %w", err)
|
||||
@@ -193,7 +193,7 @@ func (s *Service) DeployKeyToUser(key *models.SSHKey, server *models.Server, aut
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
addr := net.JoinHostPort(server.Hostname, fmt.Sprintf("%d", server.Port))
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
s.logDeployment(key.ID, server.ID, "failed", fmt.Sprintf("connection failed: %v", err))
|
||||
@@ -296,7 +296,7 @@ func (s *Service) DeployKeyToUserWithPassword(key *models.SSHKey, server *models
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
addr := net.JoinHostPort(server.Hostname, fmt.Sprintf("%d", server.Port))
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
s.logDeployment(key.ID, server.ID, "failed", fmt.Sprintf("connection failed: %v", err))
|
||||
@@ -401,7 +401,7 @@ func (s *Service) RemoveKeyFromUser(key *models.SSHKey, server *models.Server, a
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
addr := net.JoinHostPort(server.Hostname, fmt.Sprintf("%d", server.Port))
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to server for key removal: %w", err)
|
||||
@@ -456,7 +456,7 @@ func (s *Service) RemoveSystemUser(key *models.SSHKey, server *models.Server, au
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
addr := net.JoinHostPort(server.Hostname, fmt.Sprintf("%d", server.Port))
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to server for user removal: %w", err)
|
||||
@@ -526,7 +526,7 @@ func (s *Service) DisableSystemUser(key *models.SSHKey, server *models.Server, a
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
addr := net.JoinHostPort(server.Hostname, fmt.Sprintf("%d", server.Port))
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to server for user disable: %w", err)
|
||||
@@ -577,7 +577,7 @@ func (s *Service) DisableSystemUser(key *models.SSHKey, server *models.Server, a
|
||||
// TestConnection tests TCP connectivity to a server (port reachable)
|
||||
func (s *Service) TestConnection(hostname string, port int) error {
|
||||
logging.Debug("Testing TCP connection to %s:%d", hostname, port)
|
||||
addr := fmt.Sprintf("%s:%d", hostname, port)
|
||||
addr := net.JoinHostPort(hostname, fmt.Sprintf("%d", port))
|
||||
conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot reach %s: %w", addr, err)
|
||||
@@ -604,7 +604,7 @@ func (s *Service) TestSSHAuth(hostname string, port int, username string, privat
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", hostname, port)
|
||||
addr := net.JoinHostPort(hostname, fmt.Sprintf("%d", port))
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SSH authentication failed: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user