Skip to content

Commit 365851f

Browse files
committed
Release v0.6.0: Ed25519 SSH keys, CI improvements, and bug fixes
Features: - Add Ed25519 SSH host key support for improved security and performance - Add SSH port default (20022) to FarsshArguments - Configure Dependabot for security-only updates (GitHub Actions, pip) - Use version from const.py in CI workflows for consistent image tagging - Add .gitignore for .venv directory - Add TESTING.md with manual testing procedures Bug fixes: - Fix select_database() using wrong variable for cluster engine type - Rename host_key to rsa_host_key for clarity with new Ed25519 support Dependencies: - Update Python requirement to 3.9+ - Update boto3 to >=1.35 - Upgrade GitHub Actions to v6 (checkout, aws-credentials) - Upgrade Alpine base image to 3.23
1 parent 8d9a38a commit 365851f

12 files changed

Lines changed: 275 additions & 48 deletions

File tree

.github/dependabot.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Dependabot configuration
2+
# See: https://docs.github.com/en/code-security/dependabot
3+
4+
version: 2
5+
6+
updates:
7+
- package-ecosystem: "github-actions"
8+
directory: "/"
9+
schedule:
10+
interval: "weekly"
11+
open-pull-requests-limit: 0 # Only security updates, no regular version bumps
12+
13+
- package-ecosystem: "pip"
14+
directory: "/client"
15+
schedule:
16+
interval: "weekly"
17+
open-pull-requests-limit: 0 # Only security updates, no regular version bumps

.github/workflows/image.yaml

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,27 @@ on:
33
push:
44
branches:
55
- main
6-
paths:
6+
paths:
77
- '.github/workflows/image.yaml'
8+
- 'client/src/farssh/const.py'
89
- 'image/*'
910

1011
permissions:
1112
id-token: write
1213
contents: read
13-
14+
1415
jobs:
1516

1617
deploy_source:
1718
name: build and push farssh to ecr-public
1819
runs-on: ubuntu-latest
1920
steps:
20-
21+
2122
- name: Git clone the repository
22-
uses: actions/checkout@v3
23-
23+
uses: actions/checkout@v6
24+
2425
- name: configure aws credentials
25-
uses: aws-actions/configure-aws-credentials@v2
26+
uses: aws-actions/configure-aws-credentials@v6
2627
with:
2728
role-to-assume: arn:aws:iam::329261680777:role/farssh-github
2829
role-session-name: github-action-push
@@ -40,25 +41,27 @@ jobs:
4041

4142
# use qemu + buildx to build arm64 image, per
4243
# https://community.ibm.com/community/user/powerdeveloper/blogs/siddhesh-ghadi/2023/02/08/build-multi-arch-images-on-github-actions-with-bui
43-
44+
4445
- name: Set up QEMU
45-
uses: docker/setup-qemu-action@v2
46+
uses: docker/setup-qemu-action@v3
4647

4748
- name: Set up Docker Buildx
48-
uses: docker/setup-buildx-action@v2
49+
uses: docker/setup-buildx-action@v3
4950

5051
- name: build and push to ECR
5152
env:
5253
ECR_REPOSITORY: public.ecr.aws/apparentorder/farssh
53-
IMAGE_TAG: ${{ github.sha }}
5454
run: |
55-
docker buildx build --push --platform linux/arm64 --tag $ECR_REPOSITORY:$IMAGE_TAG image
56-
docker buildx build --push --platform linux/arm64 --tag $ECR_REPOSITORY:latest image
55+
VERSION=$(python -c 'from client.src.farssh.const import FARSSH_VERSION; print(FARSSH_VERSION);')
56+
DATE=$(date +%Y-%m-%d)
57+
docker buildx build --push --platform linux/arm64 --build-arg FARSSH_VERSION=$VERSION --build-arg FARSSH_DATE=$DATE --tag $ECR_REPOSITORY:$VERSION image
58+
docker buildx build --push --platform linux/arm64 --build-arg FARSSH_VERSION=$VERSION --build-arg FARSSH_DATE=$DATE --tag $ECR_REPOSITORY:latest image
5759
5860
- name: build and push to Dockerhub
5961
env:
6062
DOCKERHUB_REPOSITORY: apparentorder/farssh
61-
IMAGE_TAG: ${{ github.sha }}
6263
run: |
63-
docker buildx build --push --platform linux/arm64 --tag $DOCKERHUB_REPOSITORY:$IMAGE_TAG image
64-
docker buildx build --push --platform linux/arm64 --tag $DOCKERHUB_REPOSITORY:latest image
64+
VERSION=$(python -c 'from client.src.farssh.const import FARSSH_VERSION; print(FARSSH_VERSION);')
65+
DATE=$(date +%Y-%m-%d)
66+
docker buildx build --push --platform linux/arm64 --build-arg FARSSH_VERSION=$VERSION --build-arg FARSSH_DATE=$DATE --tag $DOCKERHUB_REPOSITORY:$VERSION image
67+
docker buildx build --push --platform linux/arm64 --build-arg FARSSH_VERSION=$VERSION --build-arg FARSSH_DATE=$DATE --tag $DOCKERHUB_REPOSITORY:latest image

.github/workflows/s3.yaml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,36 @@ on:
33
push:
44
branches:
55
- main
6-
paths:
6+
paths:
77
- '.github/workflows/s3.yaml'
8+
- 'client/src/farssh/const.py'
89
- 'cloudformation/*'
910

1011
permissions:
1112
id-token: write
1213
contents: read
13-
14+
1415
jobs:
1516

1617
deploy_source:
1718
name: copy cfn to s3
1819
runs-on: ubuntu-latest
1920
steps:
2021
- name: Git clone the repository
21-
uses: actions/checkout@v3
22+
uses: actions/checkout@v6
2223
- name: configure aws credentials
23-
uses: aws-actions/configure-aws-credentials@v2
24+
uses: aws-actions/configure-aws-credentials@v6
2425
with:
2526
role-to-assume: arn:aws:iam::329261680777:role/farssh-github
2627
role-session-name: github-action-copy-s3
2728
aws-region: eu-central-1
2829
- name: copy cfn to s3
29-
run:
30+
run: |
31+
VERSION=$(python -c 'from client.src.farssh.const import FARSSH_VERSION; print(FARSSH_VERSION);')
32+
sed -i "s|__VERSION__|$VERSION|g" cloudformation/farssh.yaml
33+
# Validate placeholder was replaced
34+
if grep -q '__VERSION__' cloudformation/farssh.yaml; then
35+
echo "ERROR: __VERSION__ placeholder not replaced"
36+
exit 1
37+
fi
3038
aws s3 sync cloudformation/ s3://farssh/cloudformation/

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.venv

client/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ readme = "../README.md"
55
license = { text = "BSD 2-clause" }
66
authors = [ { name="@apparentorder", email="apparentorder@neveragain.de" } ]
77
description = "Secure on-demand connections into AWS VPCs"
8-
requires-python = ">=3.8"
8+
requires-python = ">=3.9"
99
dependencies = [
10-
"boto3",
10+
"boto3>=1.35",
1111
]
1212
keywords = ["aws", "vpc", "ssh", "tunnel", "proxy", "rds", "postgresql", "mysql"]
1313
classifiers = [

client/src/farssh/args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def __init__(self):
1515

1616
# defaults, if not found in Parameter Store
1717
self.force_public_ipv4 = False
18+
self.ssh_port = "20022"
1819

1920
for (key, value) in get_farssh_ssm_parameters(FARSSH_ID).items():
2021
setattr(self, key, value)

client/src/farssh/aws.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,13 @@ def run_ecs_task(args, ssh_keys, farssh_id):
4545
"name": "FARSSH_SSH_AUTHORIZED_KEYS",
4646
"value": ssh_keys.login_key_pub,
4747
},
48+
{
49+
"name": "FARSSH_SSH_HOST_ED25519_KEY_BASE64",
50+
"value": base64.b64encode(bytes(ssh_keys.ed25519_host_key, "utf-8")).decode("utf-8")
51+
},
4852
{
4953
"name": "FARSSH_SSH_HOST_RSA_KEY_BASE64",
50-
"value": base64.b64encode(bytes(ssh_keys.host_key, "utf-8")).decode("utf-8")
54+
"value": base64.b64encode(bytes(ssh_keys.rsa_host_key, "utf-8")).decode("utf-8")
5155
}
5256
]
5357

@@ -140,7 +144,7 @@ def select_database(args):
140144
available += [{
141145
'identifier': cluster_id,
142146
'cluster': cluster_id,
143-
'engine': candidate_instance.get('Engine'),
147+
'engine': candidate_cluster.get('Engine'),
144148
'status': candidate_cluster.get('Status'),
145149
'hostname': candidate_cluster['Endpoint'],
146150
'port': candidate_cluster.get('Port'),

client/src/farssh/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FARSSH_VERSION = "0.5"
1+
FARSSH_VERSION = "0.6.0"
22
FARSSH_ID = 'default'
33
FARSSH_URL = 'https://github.com/apparentorder/farssh'
44

client/src/farssh/ssh.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,40 @@
66
# ----------------------------------------------------------------------
77

88
class FarsshSshKeyHandler:
9-
def __init__(self, farssh_args):
9+
def __init__(self, farssh_args) -> None:
1010
self._tempdir = tempfile.TemporaryDirectory()
1111

1212
self.farssh_args = farssh_args
1313
self.known_hosts_file = f"{self._tempdir.name}/known-hosts"
1414

15-
subprocess.run(["ssh-keygen", "-q", "-N", "", "-t", "rsa", "-f", f"{self._tempdir.name}/ssh_host_rsa_key"], check = True)
16-
subprocess.run(["ssh-keygen", "-q", "-N", "", "-t", "rsa", "-f", f"{self._tempdir.name}/ssh_login_key"], check = True)
17-
18-
self.host_key_file = f"{self._tempdir.name}/ssh_host_rsa_key"
19-
self.host_key_pub_file = f"{self._tempdir.name}/ssh_host_rsa_key.pub"
20-
self.login_key_file = f"{self._tempdir.name}/ssh_login_key"
21-
self.login_key_pub_file = f"{self._tempdir.name}/ssh_login_key.pub"
22-
23-
self.host_key = open(self.host_key_file, "r").read()
24-
self.host_key_pub = open(self.host_key_pub_file, "r").read()
25-
# self.login_key = open(self.login_key_file, "r").read() # not used
26-
self.login_key_pub = open(self.login_key_pub_file, "r").read()
27-
28-
def write_known_hosts(self, ip_address):
15+
subprocess.run(["ssh-keygen", "-q", "-N", "", "-t", "ed25519", "-f", f"{self._tempdir.name}/ssh_host_ed25519_key"], check = True)
16+
subprocess.run(["ssh-keygen", "-q", "-N", "", "-t", "rsa", "-f", f"{self._tempdir.name}/ssh_host_rsa_key"], check = True)
17+
subprocess.run(["ssh-keygen", "-q", "-N", "", "-t", "ed25519", "-f", f"{self._tempdir.name}/ssh_login_key"], check = True)
18+
19+
self.ed25519_host_key_file = f"{self._tempdir.name}/ssh_host_ed25519_key"
20+
self.ed25519_host_key_pub_file = f"{self._tempdir.name}/ssh_host_ed25519_key.pub"
21+
self.rsa_host_key_file = f"{self._tempdir.name}/ssh_host_rsa_key"
22+
self.rsa_host_key_pub_file = f"{self._tempdir.name}/ssh_host_rsa_key.pub"
23+
self.login_key_file = f"{self._tempdir.name}/ssh_login_key"
24+
self.login_key_pub_file = f"{self._tempdir.name}/ssh_login_key.pub"
25+
26+
self.ed25519_host_key = open(self.ed25519_host_key_file, "r").read()
27+
self.ed25519_host_key_pub = open(self.ed25519_host_key_pub_file, "r").read()
28+
self.rsa_host_key = open(self.rsa_host_key_file, "r").read()
29+
self.rsa_host_key_pub = open(self.rsa_host_key_pub_file, "r").read()
30+
self.login_key_pub = open(self.login_key_pub_file, "r").read()
31+
# self.login_key isn't used here, so don't read.
32+
33+
def write_known_hosts(self, ip_address) -> None:
2934
# Create temporary known-hosts file so the SSH client can verify the remote host's public key that we configured it with.
3035
# The `host` *must* be without port number when the port is 22; this seems to be a quirk of OpenSSH's known-hosts file format.
36+
# Write Ed25519 key first (preferred), then RSA (fallback for backward compatibility).
3137

3238
with open(self.known_hosts_file, "w") as f:
3339
host = ip_address
3440

3541
if self.farssh_args.ssh_port != "22":
3642
host = f"[{host}]:{self.farssh_args.ssh_port}"
3743

38-
f.write(f"{host} {self.host_key_pub}\n")
39-
44+
f.write(f"{host} {self.ed25519_host_key_pub}\n")
45+
f.write(f"{host} {self.rsa_host_key_pub}\n")

cloudformation/farssh.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ Parameters:
4646
When FarSSH runs in an IPv6 subnet, change this to the DockerHub image, as
4747
AWS ECR Public does not support image pull over IPv6.
4848
Type: String
49-
Default: public.ecr.aws/apparentorder/farssh
49+
# Placeholder __VERSION__ is replaced during deployment to S3.
50+
Default: public.ecr.aws/apparentorder/farssh:__VERSION__
5051
AllowedValues:
51-
- public.ecr.aws/apparentorder/farssh
52-
- docker.io/apparentorder/farssh
52+
- public.ecr.aws/apparentorder/farssh:__VERSION__
53+
- docker.io/apparentorder/farssh:__VERSION__
5354

5455
Conditions:
5556
AwslogsEnabled: !Equals [!Ref EnableAwslogsDriver, true]

0 commit comments

Comments
 (0)