Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] a157cbef43 Add troubleshooting section for missing rsync on remote host
Co-authored-by: Burnett01 <1208707+Burnett01@users.noreply.github.com>
2025-09-23 19:03:15 +00:00
copilot-swe-agent[bot] 9327d1cb7f Initial plan 2025-09-23 18:57:38 +00:00
19 changed files with 57 additions and 348 deletions
-5
View File
@@ -1,5 +0,0 @@
Dockerfile
LICENSE
*.md
.git*
.github*
-15
View File
@@ -1,15 +0,0 @@
root = true
[*]
charset = utf-8
end_of_line = lf
tab_width = 4
indent_size = 4
indent_style = space
max_line_length = 9999
insert_final_newline = true
trim_trailing_whitespace = true
[*.{yml,yaml}]
tab_width = 2
indent_size = 2
@@ -30,12 +30,7 @@ jobs:
with:
image: burnett01/rsync-deployments
args: --file=Dockerfile
- name: Output sarif file
run: cat snyk.sarif
- name: fix security-severity "null" to "0" for valid sarif format
run: |
sed -i 's/"security-severity": "null"/"security-severity": "0"/g' snyk.sarif
- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: snyk.sarif
+5 -8
View File
@@ -1,14 +1,11 @@
FROM alpine:3.23.0@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 AS base
RUN apk update && apk add --no-cache --upgrade rsync openssh openssl busybox
# drinternet/rsync@v1.5.1
FROM drinternet/rsync@sha256:e61f4047577b566872764fa39299092adeab691efb3884248dbd6495dc926527
# always force-upgrade rsync to get the latest security fixes
RUN apk update && apk add --no-cache --upgrade rsync
RUN rm -rf /var/cache/apk/*
COPY docker-rsync/* /bin/
RUN chmod +x /bin/agent-* /bin/ssh-* /bin/hosts-*
FROM base AS build
# Copy entrypoint
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
-1
View File
@@ -1,7 +1,6 @@
MIT License
Copyright (c) 2019-2022 Contention
Copyright (c) 2019-2025 Joshua Piper (Dr Internet)
Copyright (c) 2019-2025 Burnett01
Permission is hereby granted, free of charge, to any person obtaining a copy
+21 -42
View File
@@ -6,28 +6,24 @@
[![Dependabot Updates](https://github.com/Burnett01/rsync-deployments/actions/workflows/dependabot/dependabot-updates/badge.svg)](https://github.com/Burnett01/rsync-deployments/actions/workflows/dependabot/dependabot-updates)
This cross-platform GitHub Action deploys files in [`path`](#inputs) (relative to `GITHUB_WORKSPACE`) to a remote folder via rsync over ssh.
This GitHub Action (amd64) deploys files in `GITHUB_WORKSPACE` to a remote folder via rsync over ssh.
Use this action in a CD workflow which leaves deployable code in `GITHUB_WORKSPACE`, such [actions/checkout](https://github.com/actions/checkout).
Use this action in a CD workflow which leaves deployable code in `GITHUB_WORKSPACE`.
The base-image of this action is very small and based on **Alpine 3.23.0** (no cache) which results in fast deployments.
The base-image [drinternet/rsync](https://github.com/JoshPiper/rsync-docker/) of this action is very small and is based on Alpine 3.22.1 (no cache) which results in fast deployments.
Alpine version: [3.23.0](https://www.alpinelinux.org/posts/Alpine-3.23.0-released.html)
Rsync version: [3.4.1-r1](https://download.samba.org/pub/rsync/NEWS#3.4.1)
Alpine version: [3.22.1](https://alpinelinux.org/posts/Alpine-3.19.8-3.20.7-3.21.4-3.22.1-released.html)
Rsync version: [3.4.1-r0](https://download.samba.org/pub/rsync/NEWS#3.4.1)
---
## Inputs
- `debug`* - Whether to enable debug output. ("true" / "false") - Default: "false"
- `switches`* - The first is for any initial/required rsync flags, eg: `-avzr --delete`
- `rsh` - Remote shell commands
- `strict_hostkeys_checking` - Enables support for strict hostkeys (fingerprint) checking. ("true" / "false") - Default: "false"
- `legacy_allow_rsa_hostkeys` - Enables support for legacy RSA host keys on OpenSSH 8.8+. ("true" / "false") - Default: "false"
- `legacy_allow_rsa_hostkeys` - Enables support for legacy RSA host keys on OpenSSH 8.8+. ("true" / "false")
- `path` - The source path. Defaults to GITHUB_WORKSPACE and is relative to it
@@ -53,17 +49,7 @@ This action needs secret variables for the ssh private key of your key pair. The
For simplicity, we are using `REMOTE_*` as the secret variables throughout the examples.
## Current Version: v8 (8.0.1)
### Release channels:
| Version | Purpose | Immutable |
| ------- | ------------------ | ------------------ |
| ``v8`` | latest release (pointer to 8.x.x) | no, points to latest MINOR,PATCH |
| 8.0.1 | latest major release | yes |
| 7.1.0 | previous release | yes |
Check [SECURITY.md](SECURITY.md) for support cycles.
## Current Version: 7.1.0
## Example usage
@@ -80,9 +66,9 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v3
- name: rsync deployments
uses: burnett01/rsync-deployments@v8
uses: burnett01/rsync-deployments@7.1.0
with:
switches: -avzr --delete
path: src/
@@ -99,9 +85,9 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v3
- name: rsync deployments
uses: burnett01/rsync-deployments@v8
uses: burnett01/rsync-deployments@7.1.0
with:
switches: -avzr --delete --exclude="" --include="" --filter=""
path: src/
@@ -119,9 +105,9 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v3
- name: rsync deployments
uses: burnett01/rsync-deployments@v8
uses: burnett01/rsync-deployments@7.1.0
with:
switches: -avzr --delete
path: src/
@@ -139,9 +125,9 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v3
- name: rsync deployments
uses: burnett01/rsync-deployments@v8
uses: burnett01/rsync-deployments@7.1.0
with:
switches: -avzr --delete
path: src/
@@ -165,9 +151,9 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v3
- name: rsync deployments
uses: burnett01/rsync-deployments@v8
uses: burnett01/rsync-deployments@7.1.0
with:
switches: -avzr --delete
legacy_allow_rsa_hostkeys: "true"
@@ -277,27 +263,20 @@ sudo apk add rsync
## Versions
## Version 7.1.0
## Version 7.0.2
Check here:
- https://github.com/Burnett01/rsync-deployments/tree/7.1.0 (alpine 3.22.1)
## Version 7.0.2 (DEPRECATED)
Check here:
- https://github.com/Burnett01/rsync-deployments/tree/7.0.2 (alpine 3.22.1)
- https://github.com/Burnett01/rsync-deployments/tree/7.0.2 (alpine 3.19.1)
---
## Version 7.0.0 & 7.0.1 (EOL)
## Version 7.0.0 & 7.0.1 (DEPRECATED)
Check here:
- https://github.com/Burnett01/rsync-deployments/tree/7.0.0 (alpine 3.19.1)
- https://github.com/Burnett01/rsync-deployments/tree/7.0.1 (alpine 3.22.1)
- https://github.com/Burnett01/rsync-deployments/tree/7.0.1 (alpine 3.19.1)
---
+12 -22
View File
@@ -1,31 +1,21 @@
# Security Policy
The Docker image and code quality are regularly checked for vulnerabilities and CVEs by Snyk and CodeQL.
## Supported Versions
The following versions are currently being supported with security updates:
| Version | Supported | Rsync version | Alpine version |
| ------- | ------------------ | ------------------ | ------------------ |
| 8.0.1 | :white_check_mark: | >= 3.4.1-r1 | 3.23.0 |
| 8.0.0 | :x: EOL (due to regression #90) | >= 3.4.1-r1 | 3.23.0 |
| 7.1.0 | :white_check_mark: | >= 3.4.1-r0 | 3.22.1 |
| 7.0.2 | :warning: DEPRECATED | >= 3.4.0-r0 | 3.22.1 |
| 7.0.1 | :x: EOL | < 3.4.0 | 3.22.1 |
| 7.0.0 | :x: EOL | < 3.4.0| 3.19.1 |
| 6.x | :x: EOL |< 3.4.0| 3.17.2 |
| 5.x | :x: EOL |< 3.4.0| 3.11 - 3.14.1 - 3.15 - 3.16 - 3.17.2 |
| 4.x | :x: EOL |< 3.4.0| 3.11 |
| 3.0 | :x: EOL |< 3.4.0| N/A |
| 2.0 | :x: EOL |< 3.4.0| Ubuntu |
| 1.0 | :x: EOL |< 3.4.0| Ubuntu |
### Terminology
EOL = End of life (no support/no updates)
DEPRECATED = Close to EOL (support/no updates)
| Version | Supported | Rsync version |
| ------- | ------------------ | ------------------ |
| 7.1.0 | :white_check_mark: | >= 3.4.1 |
| 7.0.2 | :white_check_mark: | >= 3.4.0 |
| 7.0.1 | :warning: DEPRECATED | < 3.4.0 |
| 7.0.0 | :warning: DEPRECATED | < 3.4.0|
| 6.x | :x: EOL |< 3.4.0|
| 5.x | :x: EOL |< 3.4.0|
| 4.x | :x: EOL |< 3.4.0|
| 3.0 | :x: EOL |< 3.4.0|
| 2.0 | :x: EOL |< 3.4.0|
| 1.0 | :x: EOL |< 3.4.0|
## Reporting a Vulnerability
-8
View File
@@ -13,10 +13,6 @@ inputs:
description: 'Enables support for legacy RSA host keys on OpenSSH 8.8+'
required: false
default: 'false'
strict_hostkeys_checking:
description: 'Controls strict host keys checking'
required: false
default: 'false'
path:
description: 'The local path'
required: false
@@ -41,10 +37,6 @@ inputs:
description: 'The remote key passphrase'
required: false
default: ''
debug:
description: 'Debug the action'
required: false
default: 'false'
runs:
using: 'docker'
image: 'Dockerfile'
-40
View File
@@ -1,40 +0,0 @@
# Scripts
Shell-scripts to help with managing SSH agents and known hosts files.
### SSH Management
#### ssh-init
This command create the ``$HOME/.ssh`` folder with default permissions ``700``.
### SSH-Agent Management
#### agent-start
This command starts the SSH agent, if it isn't already started (SSH_AGENT_PID set or ssh agent ID file found).
It takes one optional argument, for the name of the agent to be started. Defaults to "default".
This program needs to be source'd to work correctly.
`source agent-start "default"`
#### agent-stop
This command stops the SSH agent, if it is started (SSH_AGENT_PID set or ssh agent ID file found).
It takes one optional argument, for the name of the agent to be stopped. Defaults to "default".
`agent-stop "my-agent-name"`
#### agent-add
This command adds a key to the currently running SSH agent. The key is taken from stdin, and the agent used is that in SSH_AGENT_PID.
#### agent-askpass
This command is called by ssh-add when the [SSH_ASKPASS](https://man.openbsd.org/ssh-add.1#ENVIRONMENT) variable is set active. The command returns the SSH_PASS to [ssh-askpass(1)](https://man.openbsd.org/ssh-askpass.1).
This command is ignored by ssh-add if the key does not require a passphrase.
### known_hosts management
#### hosts-init
This command creates the known_hosts file (``$HOME/.ssh/known_hosts``) with default permission ``600``.
#### hosts-add
This command adds an entry to the known hosts file, and ensures its permissions are correct. It takes one argument, which is the new key to add.
#### hosts-clear
This command truncates the known_hosts file.
-6
View File
@@ -1,6 +0,0 @@
#!/bin/sh
set -eu
source agent-start "${1:-default}"
cat - | tr -d '\r' | DISPLAY=1 SSH_ASKPASS=agent-askpass ssh-add - >/dev/null
-5
View File
@@ -1,5 +0,0 @@
#!/bin/sh
set -eu
echo "$SSH_PASS"
-21
View File
@@ -1,21 +0,0 @@
#!/bin/sh
set -eu
FOLDER=${1:-default}
STORE_PATH="/tmp/ssh-agent/$FOLDER"
mkdir -p "$STORE_PATH"
if [ -z "${SSH_AGENT_PID:-}" ]; then
if [ -f "$STORE_PATH/id" ]; then
SSH_AGENT_PID=$(cat "$STORE_PATH/id")
export SSH_AGENT_PID
SSH_AUTH_SOCK=$(cat "$STORE_PATH/sock")
export SSH_AUTH_SOCK
else
eval "$(ssh-agent)" > /dev/null
echo "$SSH_AGENT_PID" > "$STORE_PATH"/id
echo "$SSH_AUTH_SOCK" > "$STORE_PATH"/sock
fi
fi
-37
View File
@@ -1,37 +0,0 @@
#!/bin/sh
set -eu
if [ ! -z "$SSH_AGENT_PID" ]; then
# Here, the environment is set already, just kill the script.
eval $(ssh-agent -k) >/dev/null
exit $?
else
# The env isn't set, construct the file path.
FOLDER=${1:-default}
STORE_PATH="/tmp/ssh-agent/$FOLDER"
if [ ! -d "$STORE_PATH" ]; then
echo "Store Path $STORE_PATH doesn't exist!" >&2
exit 1
fi
# And check our files exist.
if [ -f "$STORE_PATH/id" ]; then
# Grab our PID and socket.
SSH_AGENT_PID=$(cat "$STORE_PATH/id")
export SSH_AGENT_PID
rm "$STORE_PATH/id"
SSH_AUTH_SOCK=$(cat "$STORE_PATH/sock")
export SSH_AUTH_SOCK
rm "$STORE_PATH/sock"
rmdir "$STORE_PATH"
eval $(ssh-agent -k) >/dev/null
exit $?
else
echo "SSH_AGENT_PID not set, $STORE_PATH/id doesn't exist!" >&2
exit 1
fi
fi
-5
View File
@@ -1,5 +0,0 @@
#!/bin/sh
set -eu
printf '%s\n' "$@" >> $HOME/.ssh/known_hosts
-5
View File
@@ -1,5 +0,0 @@
#!/bin/sh
set -eu
truncate -s 0 $HOME/.ssh/known_hosts
-6
View File
@@ -1,6 +0,0 @@
#!/bin/sh
set -eu
touch $HOME/.ssh/known_hosts
chmod 600 $HOME/.ssh/known_hosts
-5
View File
@@ -1,5 +0,0 @@
#!/bin/sh
set -eu
mkdir -m 700 $HOME/.ssh
+8 -38
View File
@@ -1,55 +1,25 @@
#!/bin/sh
set -eu
if [ "${INPUT_DEBUG:-false}" = "true" ]; then
set -x
fi
if [ -z "$(echo "$INPUT_REMOTE_PATH" | awk '{$1=$1};1')" ]; then
echo "The remote_path can not be empty. see: github.com/Burnett01/rsync-deployments/issues/44"
exit 1
fi
# Initialize SSH and known hosts.
source ssh-init
source hosts-init
# Start the SSH agent and load key.
source agent-start "$GITHUB_ACTION"
echo "$INPUT_REMOTE_KEY" | SSH_PASS="$INPUT_REMOTE_KEY_PASS" agent-add
# Add strict errors.
set -eu
# Variables.
LEGACY_RSA_HOSTKEYS=""
if [ "${INPUT_LEGACY_ALLOW_RSA_HOSTKEYS:-false}" = "true" ]; then
LEGACY_RSA_HOSTKEYS="-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
fi
LEGACY_RSA_HOSTKEYS="-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
LEGACY_RSA_HOSTKEYS=$([ "$INPUT_LEGACY_ALLOW_RSA_HOSTKEYS" = "true" ] && echo "$LEGACY_RSA_HOSTKEYS" || echo "")
STRICT_HOSTKEYS_CHECKING="-o StrictHostKeyChecking=no"
if [ "${INPUT_STRICT_HOSTKEYS_CHECKING:-false}" = "true" ]; then
STRICT_HOSTKEYS_CHECKING="-o UserKnownHostsFile=$HOME/.ssh/known_hosts -o StrictHostKeyChecking=yes"
key="$(ssh-keyscan -p "$INPUT_REMOTE_PORT" "$INPUT_REMOTE_HOST" 2>/dev/null | sed '/^#/d')" || key=""
if [ -n "$key" ]; then
# fingerprint verification
echo "$key" | ssh-keygen -lf -
# add to known hosts
echo "$key" | while IFS= read -r line; do hosts-add "$line"; done
else
echo "Warning: failed to fetch host key for $INPUT_REMOTE_HOST" >&2
exit 1
fi
fi
RSH="ssh $STRICT_HOSTKEYS_CHECKING $LEGACY_RSA_HOSTKEYS -p $INPUT_REMOTE_PORT $INPUT_RSH"
SWITCHES="$INPUT_SWITCHES"
RSH="ssh -o StrictHostKeyChecking=no $LEGACY_RSA_HOSTKEYS -p $INPUT_REMOTE_PORT $INPUT_RSH"
LOCAL_PATH="$GITHUB_WORKSPACE/$INPUT_PATH"
DSN="$INPUT_REMOTE_USER@$INPUT_REMOTE_HOST"
# Deploy.
sh -c "rsync $INPUT_SWITCHES -e '$RSH' $LOCAL_PATH $DSN:$INPUT_REMOTE_PATH"
# Clean up.
source agent-stop "$GITHUB_ACTION"
source hosts-clear
exit 0
sh -c "rsync $SWITCHES -e '$RSH' $LOCAL_PATH $DSN:$INPUT_REMOTE_PATH"
+10 -73
View File
@@ -1,13 +1,12 @@
#!/usr/bin/env bats
setup() {
# Create dummy binaries for sourcing
echo 'echo "source"' > source
# Create a dummy ssh agent and agent-add for sourcing
echo 'echo "agent started"' > agent-start
echo 'echo "key added"' > agent-add
chmod +x source agent-start agent-add
chmod +x agent-start agent-add
# Create dummy rsync binary to capture its arguments
# Create a dummy rsync to capture its arguments
echo 'echo "rsync $@"' > rsync
chmod +x rsync
@@ -15,7 +14,7 @@ setup() {
}
teardown() {
rm -f source agent-start agent-add rsync ssh-keyscan hosts-add
rm -f agent-start agent-add rsync
}
@test "fails if INPUT_REMOTE_PATH is empty" {
@@ -36,14 +35,13 @@ teardown() {
export INPUT_RSH=""
export INPUT_PATH=""
export INPUT_REMOTE_USER="user"
export INPUT_REMOTE_HOST="localhost.local"
export INPUT_REMOTE_HOST="host"
export GITHUB_WORKSPACE="/tmp"
export DSN="user@localhost.local"
export DSN="user@host"
export LOCAL_PATH="/tmp/"
run ./entrypoint.sh
[[ "${output}" == *"rsync -avz -e ssh -o StrictHostKeyChecking=no -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -p 22 /tmp/ user@localhost.local:remote/"* ]]
[[ "${output}" == *"HostKeyAlgorithms=+ssh-rsa"* ]]
}
@test "does not include legacy RSA switches when not allowed" {
@@ -57,72 +55,11 @@ teardown() {
export INPUT_RSH=""
export INPUT_PATH=""
export INPUT_REMOTE_USER="user"
export INPUT_REMOTE_HOST="localhost.local"
export INPUT_REMOTE_HOST="host"
export GITHUB_WORKSPACE="/tmp"
export DSN="user@localhost.local"
export DSN="user@host"
export LOCAL_PATH="/tmp/"
run ./entrypoint.sh
[[ "${output}" == *"rsync -avz -e ssh -o StrictHostKeyChecking=no -p 22 /tmp/ user@localhost.local:remote/"* ]]
}
@test "includes STRICT_HOSTKEYS_CHECKING switches when allowed" {
# Set a fake HOME dir
local -r HOME="/tmp"
export INPUT_LEGACY_ALLOW_RSA_HOSTKEYS="false"
export INPUT_STRICT_HOSTKEYS_CHECKING="true"
export INPUT_REMOTE_PATH="remote/"
export INPUT_REMOTE_KEY="dummy"
export INPUT_REMOTE_KEY_PASS="dummy"
export GITHUB_ACTION="dummy"
export INPUT_SWITCHES="-avz"
export INPUT_REMOTE_PORT="22"
export INPUT_RSH=""
export INPUT_PATH=""
export INPUT_REMOTE_USER="user"
export INPUT_REMOTE_HOST="localhost.local"
export GITHUB_WORKSPACE="/tmp"
export DSN="user@localhost.local"
export LOCAL_PATH="/tmp/"
# Generate a mock key pair to test ssh-keyscan (entrypoint.sh:32)
rm -f "$HOME/mockKeyPair" "$HOME/mockKeyPair.pub" \
&& ssh-keygen -t ed25519 -f "$HOME/mockKeyPair" -N '' -q -C '' \
&& mockPublicKey=$(< "$HOME/mockKeyPair.pub")
# Create dummy ssh-keyscan binary to return $mockPublicKey
echo "echo 'localhost.local $mockPublicKey #Mock 1'" > ssh-keyscan
chmod +x ssh-keyscan
# Create dummy hosts-add binary to capture its arguments
echo 'echo "hosts-add $@"' > hosts-add
chmod +x hosts-add
run ./entrypoint.sh
[[ "${output}" == *"hosts-add localhost.local ssh-ed25519"* ]]
[[ "${output}" == *"rsync -avz -e ssh -o UserKnownHostsFile=/tmp/.ssh/known_hosts -o StrictHostKeyChecking=yes -p 22 /tmp/ user@localhost.local:remote/"* ]]
}
@test "does not includes STRICT_HOSTKEYS_CHECKING switches when not allowed" {
export INPUT_LEGACY_ALLOW_RSA_HOSTKEYS="false"
export INPUT_STRICT_HOSTKEYS_CHECKING="false"
export INPUT_REMOTE_PATH="remote/"
export INPUT_REMOTE_KEY="dummy"
export INPUT_REMOTE_KEY_PASS="dummy"
export GITHUB_ACTION="dummy"
export INPUT_SWITCHES="-avz"
export INPUT_REMOTE_PORT="22"
export INPUT_RSH=""
export INPUT_PATH=""
export INPUT_REMOTE_USER="user"
export INPUT_REMOTE_HOST="localhost.local"
export GITHUB_WORKSPACE="/tmp"
export DSN="user@localhost.local"
export LOCAL_PATH="/tmp/"
run ./entrypoint.sh
[[ "${output}" == *"rsync -avz -e ssh -o StrictHostKeyChecking=no -p 22 /tmp/ user@localhost.local:remote/"* ]]
[[ "${output}" != *"HostKeyAlgorithms=+ssh-rsa"* ]]
}