add sftp_share role

This commit is contained in:
Sergey Malyuk
2025-12-16 18:03:23 +03:00
parent bbcd68cb98
commit fbd36ee3fc
21 changed files with 371 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
---
- name: Converge
hosts: all
pre_tasks:
- name: Create test group
ansible.builtin.group:
name: testgrp
state: present
- name: Ensure OpenSSH server is installed
ansible.builtin.apt:
name: openssh-server
state: present
update_cache: true
- name: Ensure the `/root/.ssh` directory exists
ansible.builtin.file:
state: directory
path: "/root/.ssh"
mode: "0700"
owner: root
group: root
- name: Provide an SSH private key for testing purposes
ansible.builtin.copy:
src: "ssh/id_ed25519"
dest: "/root/.ssh/id_ed25519"
mode: "0600"
owner: root
group: root
roles:
- role: genlab.sftp_share
sftp_username: "testusr"
sftp_pubkey: "{{ lookup('file', 'ssh/id_ed25519.pub') }}"
sftp_root: "/primary/data"
sftp_transfers_groupname: "testgrp"
sftp_permissions: "open,close,read,lstat,fstat,opendir,readdir,realpath,stat" # read-only

View File

@@ -0,0 +1,27 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest
pre_build_image: true
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint .

View File

@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBSSKh3Oc0GgnvSzCmvloaoBVpa3/ZcEUhimzih81XMUAAAAJCpucRAqbnE
QAAAAAtzc2gtZWQyNTUxOQAAACBSSKh3Oc0GgnvSzCmvloaoBVpa3/ZcEUhimzih81XMUA
AAAEDKy8xJ6QdOj+DG1oromiQ0TmwnTWFcMwjUMdqIZFd8blJIqHc5zQaCe9LMKa+WhqgF
Wlrf9lwRSGKbOKHzVcxQAAAAC3Jvb3RAdWJ1bnR1AQI=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFJIqHc5zQaCe9LMKa+WhqgFWlrf9lwRSGKbOKHzVcxQ root@ubuntu

View File

@@ -0,0 +1,91 @@
---
- name: Verify
hosts: all
gather_facts: false
any_errors_fatal: true
vars:
sftp_username: "testusr"
sftp_pubkey: "{{ lookup('file', 'ssh/id_ed25519.pub') }}"
sftp_root: "/primary/data"
sftp_transfers_groupname: "testgrp"
tasks:
- name: Gather user SFTP directory info
ansible.builtin.stat:
path: "{{ sftp_root }}/{{ sftp_username }}-uploads"
register: sftp_root_stat
- name: Assert that user SFTP directory has correct owner
ansible.builtin.assert:
that:
- sftp_root_stat.stat.pw_name == "root"
- sftp_root_stat.stat.gr_name == "root"
success_msg: "SFTP directory {{ sftp_root }}/{{ sftp_username }}-uploads has correct owner root:root"
fail_msg: |
"SFTP directory {{ sftp_root }}/{{ sftp_username }}-uploads does not have correct owner root:root,
actual owner is {{ sftp_root_stat.stat.pw_name }}:{{ sftp_root_stat.stat.gr_name }}"
- name: Gather transfers directory info
ansible.builtin.stat:
path: "{{ sftp_root }}/{{ sftp_username }}-uploads/transfers"
register: transfers_stat
- name: Assert that transfers directory has correct owner
ansible.builtin.assert:
that:
- transfers_stat.stat.pw_name == sftp_username
- transfers_stat.stat.gr_name == sftp_transfers_groupname
success_msg: "Directory {{ sftp_root }}/{{ sftp_username }}-uploads/transfers has correct owner {{ sftp_username }}:{{ sftp_transfers_groupname }}"
fail_msg: |
"Directory {{ sftp_root }}/{{ sftp_username }}-uploads/transfers does not have correct owner {{ sftp_username }}:{{ sftp_transfers_groupname }},
actual owner is {{ transfers_stat.stat.pw_name }}:{{ transfers_stat.stat.gr_name }}"
- name: Assert that transfers directory has correct permissions
ansible.builtin.assert:
that:
- transfers_stat.stat.mode == "2770"
success_msg: "Directory {{ sftp_root }}/{{ sftp_username }}-uploads/transfers has correct permissions 2770"
fail_msg: |
"Directory {{ sftp_root }}/{{ sftp_username }}-uploads/transfers does not have correct permissions 2770,
actual permissions are {{ sftp_root_stat.stat.mode }}"
- name: Get user info
ansible.builtin.getent:
database: passwd
key: "{{ sftp_username }}"
register: user_info
- name: Check user exists
ansible.builtin.assert:
that:
- user_info is defined
success_msg: "User {{ sftp_username }} exists"
fail_msg: "User {{ sftp_username }} does not exist"
- name: Check that sshd refuses non-SFTP connection attempts
changed_when: false
failed_when: ssh.stdout != expected
register: ssh
vars:
expected: "This service allows sftp connections only."
ansible.builtin.command: "ssh -l {{ sftp_username }} 127.0.0.1 -o StrictHostKeyChecking=no exit"
- name: Check that sFTP chroots into /transfers
changed_when: false
register: sftp
failed_when: sftp.stdout != expected
vars:
expected: "sftp> pwd\nRemote working directory: /transfers"
ansible.builtin.shell:
executable: /bin/bash
cmd: 'set -o pipefail; echo "pwd" | sftp -b - -o StrictHostKeyChecking=no {{ sftp_username | quote }}@127.0.0.1'
- name: Check that non-whitelisted sFTP permissions are denied
changed_when: false
register: sftp_mkdir
failed_when: sftp_mkdir.stderr != expected
vars:
expected: 'remote mkdir "/transfers/foo": Permission denied'
ansible.builtin.shell:
executable: /bin/bash
cmd: 'set -o pipefail; echo "mkdir foo" | sftp -b - -o StrictHostKeyChecking=no {{ sftp_username | quote }}@127.0.0.1'