From 1366f1473a2ce78bfca66569bdd8110bacbb76dd Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Sat, 2 Nov 2019 19:10:39 +0100 Subject: [PATCH] initial commit --- .drone.jsonnet | 148 ++++++++++++++++++++++ .drone.yml | 148 ++++++++++++++++++++++ .gitignore | 11 ++ HEADER.md | 4 + LICENSE | 8 ++ README.md | 2 + defaults/main.yml | 53 ++++++++ handlers/main.yml | 8 ++ meta/main.yml | 13 ++ molecule/default/create.yml | 87 +++++++++++++ molecule/default/destroy.yml | 54 ++++++++ molecule/default/molecule.yml | 24 ++++ molecule/default/playbook.yml | 5 + molecule/default/prepare.yml | 9 ++ molecule/default/tests/test_default.py | 18 +++ molecule/pytest.ini | 3 + tasks/main.yml | 13 ++ tasks/ssh_2fa.yml | 42 ++++++ tasks/ssh_default.yml | 30 +++++ tasks/ssh_univention.yml | 37 ++++++ templates/ssh/sshd_config.j2 | 169 +++++++++++++++++++++++++ 21 files changed, 886 insertions(+) create mode 100644 .drone.jsonnet create mode 100644 .drone.yml create mode 100644 .gitignore create mode 100644 HEADER.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 defaults/main.yml create mode 100644 handlers/main.yml create mode 100644 meta/main.yml create mode 100644 molecule/default/create.yml create mode 100644 molecule/default/destroy.yml create mode 100644 molecule/default/molecule.yml create mode 100644 molecule/default/playbook.yml create mode 100644 molecule/default/prepare.yml create mode 100644 molecule/default/tests/test_default.py create mode 100644 molecule/pytest.ini create mode 100644 tasks/main.yml create mode 100644 tasks/ssh_2fa.yml create mode 100644 tasks/ssh_default.yml create mode 100644 tasks/ssh_univention.yml create mode 100644 templates/ssh/sshd_config.j2 diff --git a/.drone.jsonnet b/.drone.jsonnet new file mode 100644 index 0000000..71720ae --- /dev/null +++ b/.drone.jsonnet @@ -0,0 +1,148 @@ +local PipelineLinting = { + kind: "pipeline", + name: "linting", + platform: { + os: "linux", + arch: "amd64", + }, + steps: [ + { + name: "ansible-later", + image: "xoxys/ansible-later:latest", + environment: { + PY_COLORS: 1 + }, + commands: [ + "git clone https://gitea.rknet.org/ansible/ansible-later-policy.git ~/policy", + "ansible-later -c ~/policy/config.yml" + ], + }, + ], + trigger: { + ref: ["refs/heads/master", "refs/tags/**", "refs/pull/**"], + }, +}; + +local PipelineDeployment = { + kind: "pipeline", + name: "deployment", + platform: { + os: "linux", + arch: "amd64", + }, + concurrency: { + limit: 1 + }, + workspace: { + base: "/drone/src", + path: "xoxys.sshd" + }, + steps: [ + { + name: "ansible-molecule", + image: "xoxys/molecule:do-linux-amd64", + environment: { + DO_API_KEY: { "from_secret": "do_api_key" }, + USER: "root", + MOLECULE_CUSTOM_MODULES_REPO: "https://gitea.rknet.org/ansible/custom_modules", + MOLECULE_CUSTOM_FILTERS_REPO: "https://gitea.rknet.org/ansible/custom_filters", + PY_COLORS: 1 + }, + commands: [ + "/bin/bash /docker-entrypoint.sh", + "molecule test -s default", + ], + }, + ], + depends_on: [ + "linting", + ], + trigger: { + ref: ["refs/heads/master", "refs/tags/**"], + }, +}; + +local PipelineDocumentation = { + kind: "pipeline", + name: "documentation", + platform: { + os: "linux", + arch: "amd64", + }, + steps: [ + { + name: "ansible-doctor", + image: "xoxys/ansible-doctor:latest", + environment: { + ANSIBLE_DOCTOR_LOG_LEVEL: "INFO", + ANSIBLE_DOCTOR_FORCE_OVERWRITE: true, + ANSIBLE_DOCTOR_EXCLUDE_FILES: "molecule/", + ANSIBLE_DOCTOR_CUSTOM_HEADER: "HEADER.md", + PY_COLORS: 1 + }, + }, + { + name: "push-to-repo", + image: "plugins/git-action:latest", + settings: { + actions: ["commit", "push"], + author_email: "shipper@rknet.org", + author_name: "DroneShipper", + branch: "master", + message: "[SKIP CI] update readme", + remote: "https://gitea.rknet.org/ansible/xoxys.sshd", + netrc_machine: "gitea.rknet.org", + netrc_username: {"from_secret": "gitea_username"}, + netrc_password: {"from_secret": "gitea_token"}, + }, + when: { + ref: ["refs/heads/master"], + }, + }, + ], + depends_on: [ + "deployment", + ], + trigger: { + ref: ["refs/heads/master", "refs/tags/**", "refs/pull/**"], + }, +}; + +local PipelineNotification= { + kind: "pipeline", + name: "notification", + platform: { + os: "linux", + arch: "amd64", + }, + clone: { + disable: true, + }, + steps: [ + { + name: "matrix", + image: "plugins/matrix", + settings: { + homeserver: { "from_secret": "matrix_homeserver" }, + roomid: { "from_secret": "matrix_roomid" }, + template: "Status: **{{ build.status }}**
Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}
Message: {{ build.message }}", + username: { "from_secret": "matrix_username" }, + password: { "from_secret": "matrix_password" }, + }, + }, + ], + depends_on: [ + "documentation", + ], + trigger: { + status: [ "success", "failure" ], + ref: ["refs/heads/master", "refs/tags/**"], + }, +}; + +[ + PipelineLinting, + PipelineDeployment, + PipelineDocumentation, + PipelineNotification, +] diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..5474b83 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,148 @@ +--- +kind: pipeline +name: linting + +platform: + os: linux + arch: amd64 + +steps: +- name: ansible-later + image: xoxys/ansible-later:latest + commands: + - git clone https://gitea.rknet.org/ansible/ansible-later-policy.git ~/policy + - ansible-later -c ~/policy/config.yml + environment: + PY_COLORS: 1 + +trigger: + ref: + - refs/heads/master + - refs/tags/** + - refs/pull/** + +--- +kind: pipeline +name: deployment + +platform: + os: linux + arch: amd64 + +concurrency: + limit: 1 + +workspace: + base: /drone/src + path: xoxys.sshd + +steps: +- name: ansible-molecule + image: xoxys/molecule:do-linux-amd64 + commands: + - /bin/bash /docker-entrypoint.sh + - molecule test -s default + environment: + DO_API_KEY: + from_secret: do_api_key + MOLECULE_CUSTOM_FILTERS_REPO: https://gitea.rknet.org/ansible/custom_filters + MOLECULE_CUSTOM_MODULES_REPO: https://gitea.rknet.org/ansible/custom_modules + PY_COLORS: 1 + USER: root + +trigger: + ref: + - refs/heads/master + - refs/tags/** + +depends_on: +- linting + +--- +kind: pipeline +name: documentation + +platform: + os: linux + arch: amd64 + +steps: +- name: ansible-doctor + image: xoxys/ansible-doctor:latest + environment: + ANSIBLE_DOCTOR_CUSTOM_HEADER: HEADER.md + ANSIBLE_DOCTOR_EXCLUDE_FILES: molecule/ + ANSIBLE_DOCTOR_FORCE_OVERWRITE: true + ANSIBLE_DOCTOR_LOG_LEVEL: INFO + PY_COLORS: 1 + +- name: push-to-repo + image: plugins/git-action:latest + settings: + actions: + - commit + - push + author_email: shipper@rknet.org + author_name: DroneShipper + branch: master + message: "[SKIP CI] update readme" + netrc_machine: gitea.rknet.org + netrc_password: + from_secret: gitea_token + netrc_username: + from_secret: gitea_username + remote: https://gitea.rknet.org/ansible/xoxys.sshd + when: + ref: + - refs/heads/master + +trigger: + ref: + - refs/heads/master + - refs/tags/** + - refs/pull/** + +depends_on: +- deployment + +--- +kind: pipeline +name: notification + +platform: + os: linux + arch: amd64 + +clone: + disable: true + +steps: +- name: matrix + image: plugins/matrix + settings: + homeserver: + from_secret: matrix_homeserver + password: + from_secret: matrix_password + roomid: + from_secret: matrix_roomid + template: "Status: **{{ build.status }}**
Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}
Message: {{ build.message }}" + username: + from_secret: matrix_username + +trigger: + ref: + - refs/heads/master + - refs/tags/** + status: + - success + - failure + +depends_on: +- documentation + +--- +kind: signature +hmac: 2bfb1fd991fecdc2875b843a6e9fadb7e42a3920c1a2884c69d7b9ddf8ac52eb + +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d97b7cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# ---> Ansible +*.retry +plugins +library + +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + diff --git a/HEADER.md b/HEADER.md new file mode 100644 index 0000000..b1f2143 --- /dev/null +++ b/HEADER.md @@ -0,0 +1,4 @@ +# xoxys.sshd + +[![Build Status](https://drone.rknet.org/api/badges/ansible/xoxys.sshd/status.svg)](https://drone.rknet.org/ansible/xoxys.sshd) + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..472ac23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +MIT License +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0c1f7c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# xoxys.sshd + diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..4231f1f --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,53 @@ +--- +sshd_protocol: 2 +sshd_permit_root_login: 'yes' +sshd_permit_empty_passwords: 'no' +sshd_password_authentication: 'no' +sshd_gssapi_authentication: 'yes' +sshd_strict_modes: 'yes' +sshd_allow_groups: [] +sshd_ignore_rhosts: 'yes' +sshd_hostbased_authentication: 'no' +sshd_client_alive_interval: 900 +sshd_client_alive_count_max: 0 +sshd_ciphers: + - chacha20-poly1305@openssh.com + - aes256-gcm@openssh.com + - aes128-gcm@openssh.com + - aes256-ctr + - aes192-ctr + - aes128-ctr +sshd_kex: + - curve25519-sha256@libssh.org + - diffie-hellman-group-exchange-sha256 +sshd_moduli_minimum: 2048 +sshd_macs: + - hmac-sha2-512-etm@openssh.com + - hmac-sha2-256-etm@openssh.com + - hmac-ripemd160-etm@openssh.com + - umac-128-etm@openssh.com + - hmac-sha2-512 + - hmac-sha2-256 + - hmac-ripemd160 +sshd_allow_agent_forwarding: 'no' +sshd_allow_tcp_forwarding: 'yes' +sshd_compression: delayed +sshd_log_level: INFO +sshd_max_auth_tries: 6 +sshd_max_sessions: 10 +sshd_tcp_keep_alive: 'yes' +sshd_use_dns: 'yes' + +# @var sshd_challenge_response_authentication:description: > +# If you disable password auth you should only disable +# ChallengeResponseAuth. +# @end +sshd_challenge_response_authentication: 'no' + +# @var sshd_google_auth_enabled:description: > +# Google Authenticator required ChallengeResponseAuth! +# @end +sshd_google_auth_enabled: False +# @var sshd_google_auth_exclude_group:description: Exclude a group from 2FA auth +# @var sshd_google_auth_exclude_group:example: $ "my_group" +# @var sshd_google_auth_exclude_group: $ "_unset_" diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..30baeb0 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,8 @@ +--- +- name: Restart ssh server + service: + name: sshd + state: restarted + listen: __sshd_restart + become: True + become_user: root diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..9914e00 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,13 @@ +# Standards: 0.1 +--- +galaxy_info: + author: xoxys + description: + license: MIT + min_ansible_version: 2.4 + platforms: + - name: EL + versions: + - 7 + galaxy_tags: +dependencies: diff --git a/molecule/default/create.yml b/molecule/default/create.yml new file mode 100644 index 0000000..41f112d --- /dev/null +++ b/molecule/default/create.yml @@ -0,0 +1,87 @@ +--- +- name: Create + hosts: localhost + connection: local + gather_facts: false + no_log: "{{ molecule_no_log }}" + vars: + ssh_user: root + ssh_port: 22 + + keypair_name: molecule_key + keypair_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key" + tasks: + - name: Create local keypair + user: + name: "{{ lookup('env', 'USER') }}" + generate_ssh_key: true + ssh_key_file: "{{ keypair_path }}" + register: local_keypair + + - name: Create remote keypair + digital_ocean_sshkey: + name: "{{ keypair_name }}" + ssh_pub_key: "{{ local_keypair.ssh_public_key }}" + state: present + register: remote_keypair + + - name: Create molecule instance(s) + digital_ocean_droplet: + name: "{{ item.name }}" + unique_name: true + region: "{{ item.region_id }}" + image: "{{ item.image_id }}" + size: "{{ item.size_id }}" + ssh_keys: "{{ remote_keypair.data.ssh_key.id }}" + wait: true + wait_timeout: 300 + state: present + register: server + loop: "{{ molecule_yml.platforms }}" + async: 7200 + poll: 0 + + - name: Wait for instance(s) creation to complete + async_status: + jid: "{{ item.ansible_job_id }}" + register: digitalocean_jobs + until: digitalocean_jobs.finished + retries: 300 + loop: "{{ server.results }}" + + # Mandatory configuration for Molecule to function. + + - name: Populate instance config dict + set_fact: + instance_conf_dict: { + 'instance': "{{ item.data.droplet.name }}", + 'address': "{{ item.data.ip_address }}", + 'user': "{{ ssh_user }}", + 'port': "{{ ssh_port }}", + 'identity_file': "{{ keypair_path }}", + 'droplet_id': "{{ item.data.droplet.id }}", + 'ssh_key_id': "{{ remote_keypair.data.ssh_key.id }}", + } + loop: "{{ digitalocean_jobs.results }}" + register: instance_config_dict + when: server.changed | bool + + - name: Convert instance config dict to a list + set_fact: + instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" + when: server.changed | bool + + - name: Dump instance config + copy: + content: "{{ instance_conf | to_json | from_json | molecule_to_yaml | molecule_header }}" + dest: "{{ molecule_instance_config }}" + when: server.changed | bool + + - name: Wait for SSH + wait_for: + port: "{{ ssh_port }}" + host: "{{ item.address }}" + search_regex: SSH + delay: 10 + timeout: 320 + loop: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}" diff --git a/molecule/default/destroy.yml b/molecule/default/destroy.yml new file mode 100644 index 0000000..19c8c93 --- /dev/null +++ b/molecule/default/destroy.yml @@ -0,0 +1,54 @@ +--- +- name: Destroy + hosts: localhost + connection: local + gather_facts: false + no_log: "{{ molecule_no_log }}" + tasks: + - block: + - name: Populate instance config + set_fact: + instance_conf: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}" + skip_instances: false + rescue: + - name: Populate instance config when file missing + set_fact: + instance_conf: {} + skip_instances: true + + - name: Destroy molecule instance(s) + digital_ocean_droplet: + name: "{{ item.instance }}" + id: "{{ item.droplet_id }}" + state: absent + register: server + loop: "{{ instance_conf | flatten(levels=1) }}" + when: not skip_instances + async: 7200 + poll: 0 + + - name: Wait for instance(s) deletion to complete + async_status: + jid: "{{ item.ansible_job_id }}" + register: digitalocean_jobs + until: digitalocean_jobs.finished + retries: 300 + loop: "{{ server.results }}" + + - name: Delete remote keypair + digital_ocean_sshkey: + fingerprint: "{{ item.ssh_key_id }}" + state: absent + loop: "{{ instance_conf | flatten(levels=1) }}" + + # Mandatory configuration for Molecule to function. + + - name: Populate instance config + set_fact: + instance_conf: {} + + - name: Dump instance config + copy: + content: "{{ instance_conf | molecule_to_yaml | molecule_header }}" + dest: "{{ molecule_instance_config }}" + when: server.changed | bool diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 0000000..a7848d3 --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,24 @@ +--- +dependency: + name: galaxy +driver: + name: digitalocean +platforms: + - name: centos7-sshd + region_id: fra1 + image_id: centos-7-x64 + size_id: s-1vcpu-1gb +lint: + name: yamllint + enabled: False +provisioner: + name: ansible + lint: + name: ansible-lint + enabled: False +verifier: + name: testinfra + lint: + name: flake8 + options: + max-line-length: 120 diff --git a/molecule/default/playbook.yml b/molecule/default/playbook.yml new file mode 100644 index 0000000..ae4e544 --- /dev/null +++ b/molecule/default/playbook.yml @@ -0,0 +1,5 @@ +--- +- name: Converge + hosts: all + roles: + - role: xoxys.sshd diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml new file mode 100644 index 0000000..4b18d48 --- /dev/null +++ b/molecule/default/prepare.yml @@ -0,0 +1,9 @@ +--- +- name: Prepare + hosts: all + gather_facts: false + tasks: + - name: Install python for Ansible + raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal) + become: true + changed_when: false diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py new file mode 100644 index 0000000..d38afde --- /dev/null +++ b/molecule/default/tests/test_default.py @@ -0,0 +1,18 @@ +import os + +import testinfra.utils.ansible_runner + +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') + + +def test_sshd_config_file(host): + sshd = host.file("/etc/ssh/sshd_config") + + assert sshd.exists + assert sshd.user == "root" + assert sshd.group == "root" + assert oct(sshd.mode) == "0600" diff --git a/molecule/pytest.ini b/molecule/pytest.ini new file mode 100644 index 0000000..c24fe5b --- /dev/null +++ b/molecule/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +filterwarnings = + ignore::DeprecationWarning diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..918a0fb --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,13 @@ +--- +- include_tasks: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - "ssh_{{ ansible_lsb.id | default('') | lower }}.yml" + - "ssh_{{ ansible_os_family | lower }}.yml" + - "ssh_default.yml" + paths: + - "tasks" + +- include_tasks: ssh_2fa.yml + when: sshd_google_auth_enabled | bool diff --git a/tasks/ssh_2fa.yml b/tasks/ssh_2fa.yml new file mode 100644 index 0000000..1ad8d91 --- /dev/null +++ b/tasks/ssh_2fa.yml @@ -0,0 +1,42 @@ +--- +- block: + - name: Install google authenticator PAM module + yum: + name: google-authenticator + state: present + + - name: Add google auth module to PAM + pamd: + name: sshd + type: account + control: required + module_path: pam_nologin.so + new_type: auth + new_control: required + new_module_path: pam_google_authenticator.so + state: before + + - name: Skip google auth for specific group + pamd: + name: sshd + type: auth + control: required + module_path: pam_google_authenticator.so + new_type: auth + new_control: "[success=done default=ignore]" + new_module_path: pam_succeed_if.so + module_arguments: + - user + - ingroup + - "{{ sshd_google_auth_exclude_group }}" + state: "{{ 'before' if sshd_google_auth_exclude_group is defined else 'absent' }}" + + - name: Remove password auth from PAM + pamd: + name: sshd + type: auth + control: substack + module_path: password-auth + state: absent + become: True + become_user: root diff --git a/tasks/ssh_default.yml b/tasks/ssh_default.yml new file mode 100644 index 0000000..8265a2a --- /dev/null +++ b/tasks/ssh_default.yml @@ -0,0 +1,30 @@ +--- +- block: + - name: Hardening sshd config + template: + src: etc/ssh/sshd_config.j2 + dest: /etc/ssh/sshd_config + owner: root + group: root + mode: 0600 + notify: __sshd_restart + + - name: Check if /etc/ssh/moduli contains weak DH parameters + shell: awk '$5 < {{ sshd_moduli_minimum }}' /etc/ssh/moduli + register: __sshd_register_moduli + changed_when: False + check_mode: no + + - name: Remove all small primes + shell: awk '$5 >= {{ sshd_moduli_minimum }}' /etc/ssh/moduli > /etc/ssh/moduli.new ; + [ -r /etc/ssh/moduli.new -a -s /etc/ssh/moduli.new ] && mv /etc/ssh/moduli.new /etc/ssh/moduli || true + notify: __sshd_restart + when: __sshd_register_moduli.stdout + + - name: Create SSH Usergroup + group: + name: "{{ item }}" + state: present + loop: "{{ sshd_allow_groups }}" + become: True + become_user: root diff --git a/tasks/ssh_univention.yml b/tasks/ssh_univention.yml new file mode 100644 index 0000000..7291dd2 --- /dev/null +++ b/tasks/ssh_univention.yml @@ -0,0 +1,37 @@ +--- +- block: + - name: Hardening sshd config + ucr: + path: "{{ item.path }}" + value: "{{ item.value }}" + loop: + - { path: sshd/permitroot, value: "{{ sshd_permit_root_login | default('') }}" } + - { path: sshd/PermitEmptyPasswords, value: "{{ sshd_permit_empty_passwords | default('') }}" } + - { path: sshd/permitroot, value: "{{ sshd_permit_root_login | default('') }}" } + - { path: sshd/passwordauthentication, value: "{{ sshd_password_authentication | default('') }}" } + - { path: sshd/challengeresponse, value: "{{ sshd_password_authentication | default('') }}" } + - { path: sshd/IgnoreRhosts, value: "{{ sshd_ignore_rhosts | default('') }}" } + - { path: sshd/HostbasedAuthentication, value: "{{ sshd_hostbased_authentication | default('') }}" } + - { path: sshd/ClientAliveInterval, value: "{{ sshd_client_alive_interval | default('') }}" } + - { path: sshd/ClientAliveCountMax, value: "{{ sshd_client_alive_count_max | default('') }}" } + - { path: sshd/Ciphers, value: "{{ sshd_ciphers | default('[]') | join(',') }}" } + - { path: sshd/KexAlgorithms, value: "{{ sshd_kex | default('[]') | join(',') }}" } + - { path: sshd/MACs, value: "{{ sshd_macs | default('[]') | join(',') }}" } + loop_control: + label: "variable: {{ item.path }}={{ item.value }}" + notify: __sshd_restart + + - name: Set allowed ssh groups + ucr: + path: "auth/sshd/group/{{ item }}" + value: "yes" + loop: "{{ sshd_allow_groups }}" + + - name: Create SSH Usergroup + group: + name: "{{ item }}" + system: 'yes' + state: present + loop: "{{ sshd_allow_groups }}" + become: True + become_user: root diff --git a/templates/ssh/sshd_config.j2 b/templates/ssh/sshd_config.j2 new file mode 100644 index 0000000..a6b8858 --- /dev/null +++ b/templates/ssh/sshd_config.j2 @@ -0,0 +1,169 @@ +#jinja2: lstrip_blocks: True +{{ ansible_managed | comment }} +# $OpenBSD: sshd_config,v 1.93 2014/01/10 05:59:19 djm Exp $ + +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# This sshd was compiled with PATH=/usr/local/bin:/usr/bin + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options override the +# default value. + +# If you want to change the port on a SELinux system, you have to tell +# SELinux about this change. +# semanage port -a -t ssh_port_t -p tcp #PORTNUMBER +# +#Port 22 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +# The default requires explicit activation of protocol 1 +Protocol {{ sshd_protocol }} + +# HostKey for protocol version 1 +#HostKey /etc/ssh/ssh_host_key +# HostKeys for protocol version 2 +HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_dsa_key +HostKey /etc/ssh/ssh_host_ecdsa_key +HostKey /etc/ssh/ssh_host_ed25519_key + +# Lifetime and size of ephemeral version 1 server key +#KeyRegenerationInterval 1h +#ServerKeyBits 1024 + +# Ciphers and keying +#RekeyLimit default none +Ciphers {{ sshd_ciphers | join(',') }} +KexAlgorithms {{ sshd_kex | join(',') }} +MACs {{ sshd_macs | join(',') }} + +# Logging +# obsoletes QuietMode and FascistLogging +#SyslogFacility AUTH +SyslogFacility AUTHPRIV +LogLevel {{ sshd_log_level }} + +# Authentication: + +#LoginGraceTime 2m +PermitRootLogin {{ sshd_permit_root_login }} +StrictModes {{ sshd_strict_modes }} +{% if sshd_allow_groups %} +AllowGroups {{ sshd_allow_groups|join(',') }} +{% endif %} +MaxAuthTries {{ sshd_max_auth_tries }} +MaxSessions {{ sshd_max_sessions }} + +#RSAAuthentication yes +#PubkeyAuthentication yes + +# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 +# but this is overridden so installations will only check .ssh/authorized_keys +AuthorizedKeysFile .ssh/authorized_keys + +#AuthorizedPrincipalsFile none + +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#RhostsRSAAuthentication no +# similar for protocol version 2 +HostbasedAuthentication {{ sshd_hostbased_authentication }} +# Change to yes if you don't trust ~/.ssh/known_hosts for +# RhostsRSAAuthentication and HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +IgnoreRhosts {{ sshd_ignore_rhosts }} + +{% if sshd_google_auth_enabled %} +# Force public key auth then ask for google auth code +AuthenticationMethods publickey,keyboard-interactive + +{% endif %} +# To disable tunneled clear text passwords, change to no here! +PasswordAuthentication {{ sshd_password_authentication }} +PermitEmptyPasswords {{ sshd_permit_empty_passwords }} + +# Change to no to disable s/key passwords +ChallengeResponseAuthentication {{ sshd_challenge_response_authentication }} + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no +#KerberosUseKuserok yes + +# GSSAPI options +GSSAPIAuthentication {{ sshd_gssapi_authentication }} +GSSAPICleanupCredentials no +#GSSAPIStrictAcceptorCheck yes +#GSSAPIKeyExchange no +#GSSAPIEnablek5users no + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via ChallengeResponseAuthentication may bypass +# the setting of "PermitRootLogin without-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and ChallengeResponseAuthentication to 'no'. +# WARNING: 'UsePAM no' is not supported in Red Hat Enterprise Linux and may cause several +# problems. +UsePAM yes + +AllowAgentForwarding {{ sshd_allow_agent_forwarding }} +AllowTcpForwarding {{ sshd_allow_tcp_forwarding }} +#GatewayPorts no +X11Forwarding {{ sshd_x11_forwarding }} +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PermitTTY yes +#PrintMotd yes +#PrintLastLog no +TCPKeepAlive {{ sshd_tcp_keep_alive }} +#UseLogin no +UsePrivilegeSeparation sandbox +#PermitUserEnvironment no +Compression {{ sshd_compression }} +ClientAliveInterval {{ sshd_client_alive_interval }} +ClientAliveCountMax {{ sshd_client_alive_count_max }} +#ShowPatchLevel no +UseDNS {{ sshd_use_dns }} +#PidFile /var/run/sshd.pid +#MaxStartups 10:30:100 +#PermitTunnel no +#ChrootDirectory none +#VersionAddendum none + +# no default banner path +#Banner none + +# Accept locale-related environment variables +AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES +AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT +AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE +AcceptEnv XMODIFIERS + +# override default of no subsystems +Subsystem sftp /usr/libexec/openssh/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# PermitTTY no +# ForceCommand cvs server + +{% if sshd_google_auth_exclude_group is defined %} +Match User {{ sshd_google_auth_exclude_group }} + AuthenticationMethods publickey +{% endif %}