diff --git a/roles/requirements.yml b/roles/requirements.yml index df61381..e3e5524 100644 --- a/roles/requirements.yml +++ b/roles/requirements.yml @@ -5,6 +5,12 @@ # MAD PROPS to geerlingguy; if for some reason you end up reading this, hit me # up and I'll buy you a beer or a pizza or something. +# SSHD +# Upstream: https://github.com/willshersystems/ansible-sshd +- src: willshersystems.sshd + version: v0.12.0 + name: sshd + # DHCP # Upstream: https://github.com/bertvv/ansible-role-dhcp - src: bertvv.dhcp diff --git a/roles/sshd/.ansible-lint b/roles/sshd/.ansible-lint new file mode 100644 index 0000000..6d6011d --- /dev/null +++ b/roles/sshd/.ansible-lint @@ -0,0 +1,2 @@ +warn_list: # or 'skip_list' to silence them completely │ + - '106' # Role name {} does not match ``^[a-z][a-z0-9_]+$`` pattern diff --git a/roles/sshd/.github/workflows/ansible-centos7.yml b/roles/sshd/.github/workflows/ansible-centos7.yml new file mode 100644 index 0000000..9eebd2b --- /dev/null +++ b/roles/sshd/.github/workflows/ansible-centos7.yml @@ -0,0 +1,16 @@ +name: Run tests on CentOS 7 + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: ansible check with centos:7 + uses: roles-ansible/check-ansible-centos-centos7-action@master + with: + group: local + hosts: localhost + targets: "tests/*.yml" diff --git a/roles/sshd/.github/workflows/ansible-centos8.yml b/roles/sshd/.github/workflows/ansible-centos8.yml new file mode 100644 index 0000000..9afa024 --- /dev/null +++ b/roles/sshd/.github/workflows/ansible-centos8.yml @@ -0,0 +1,16 @@ +name: Run tests on CentOS 8 + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: ansible check with centos:8 + uses: roles-ansible/check-ansible-centos-centos8-action@master + with: + group: local + hosts: localhost + targets: "tests/*.yml" diff --git a/roles/sshd/.github/workflows/ansible-fedora.yml b/roles/sshd/.github/workflows/ansible-fedora.yml new file mode 100644 index 0000000..6162807 --- /dev/null +++ b/roles/sshd/.github/workflows/ansible-fedora.yml @@ -0,0 +1,17 @@ +name: Run tests on Fedora latest + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Important: This sets up your GITHUB_WORKSPACE environment variable + - uses: actions/checkout@v2 + + - name: ansible check with fedora:latest + uses: roles-ansible/check-ansible-fedora-latest-action@master + with: + group: local + hosts: localhost + targets: "tests/*.yml" diff --git a/roles/sshd/.github/workflows/ansible-lint.yml b/roles/sshd/.github/workflows/ansible-lint.yml new file mode 100644 index 0000000..50f0665 --- /dev/null +++ b/roles/sshd/.github/workflows/ansible-lint.yml @@ -0,0 +1,38 @@ +name: Ansible Lint # feel free to pick your own name + +on: [push, pull_request] + +jobs: +# test-ansible28: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - name: Lint Ansible Playbook +# uses: ansible/ansible-lint-action@master +# with: +# targets: "tests/test_*.yml" +# override-deps: | +# ansible==2.8 +# args: "" +# test-ansible29: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - name: Lint Ansible Playbook +# uses: ansible/ansible-lint-action@master +# with: +# targets: "tests/test_*.yml" +# override-deps: | +# ansible==2.9 +# args: "" + test-ansible210: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Lint Ansible Playbook + uses: ansible/ansible-lint-action@master + with: + targets: "tests/test_*.yml" + override-deps: | + ansible==2.10 + args: "" diff --git a/roles/sshd/.gitignore b/roles/sshd/.gitignore new file mode 100644 index 0000000..5d14e0b --- /dev/null +++ b/roles/sshd/.gitignore @@ -0,0 +1,2 @@ +.vagrant +tests/test.retry diff --git a/roles/sshd/.pre-commit-config.yaml b/roles/sshd/.pre-commit-config.yaml new file mode 100644 index 0000000..d561167 --- /dev/null +++ b/roles/sshd/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +--- +repos: + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.24.2 + hooks: + - id: yamllint + files: \.(yaml|yml)$ + types: [file, yaml] + entry: yamllint --strict + - repo: https://github.com/ansible/ansible-lint.git + rev: v4.3.5 + hooks: + - id: ansible-lint + files: \.(yaml|yml)$ diff --git a/roles/sshd/.travis.yml b/roles/sshd/.travis.yml new file mode 100644 index 0000000..1d08e2f --- /dev/null +++ b/roles/sshd/.travis.yml @@ -0,0 +1,80 @@ +--- +os: linux +dist: focal +language: python +addons: + apt_packages: + - yamllint + +notifications: + webhooks: https://galaxy.ansible.com/api/v1/notifications/ + +before_install: + - sudo -H pip3 install ansible + +install: + # Add ansible.cfg to pick up roles path. + - "{ echo '[defaults]'; echo 'roles_path = ../'; echo 'deprecation_warnings=False'; } >> ansible.cfg" + +script: + # Test 0a: Check the roles syntax. + - "ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_default.yml --syntax-check" + + # Test 0b: Run yamllint with galaxy configuration to avoid quality score penalty + - wget https://raw.githubusercontent.com/ansible/galaxy/devel/galaxy/importer/linters/yamllint.yaml + - "yamllint -c yamllint.yaml **/*.yml" + + # Test 1a: Run the role + - "ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_default.yml --connection=local --become -v" + + # Test 1b: Run the role through include + - "ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_default_include.yml --connection=local --become -v" + + # Test 2: Run the role/playbook again, checking to make sure it's idempotent. + - > + ansible-playbook -i tests/inventory tests/tests_default.yml --connection=local --become | grep -q 'changed=0.*failed=0' + && (echo 'Idempotence test: pass' && exit 0) + || (echo 'Idempotence test: fail' && exit 1) + + # Test 3: Check we can set arbitrary configuration options + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_set_common.yml --connection=local --become -v + && (echo 'Common variables test: pass' && exit 0) + || (echo 'Common variables test: fail' && exit 1) + + # Test 4: Check if we set uncommon or unsupported configuration option, it will not fail hard + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_set_uncommon.yml --connection=local --become -v + && (echo 'Uncommon configuration test: pass' && exit 0) + || (echo 'Uncommon configuration test: fail' && exit 1) + + # Test 5: Make sure we can modify other files, for example for inclusion + # in the main sshd_config or second sshd service + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_alternative_file.yml --connection=local --become -v + && (echo 'Alternative configuration file test: pass' && exit 0) + || (echo 'Alternative configuration file test: fail' && exit 1) + + # Test 6: Test match blocks generators + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_match.yml --connection=local --become -v + && (echo 'Match blocks test: pass' && exit 0) + || (echo 'Match blocks test: fail' && exit 1) + + # Test 7: Test match blocks generators with iteration + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_match_iterate.yml --connection=local --become -v + && (echo 'Match blocks with iteration test: pass' && exit 0) + || (echo 'Match blocks with iteration test: fail' && exit 1) + + # Test 8: Test hostkeys can be generated by this role + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_hostkeys.yml --connection=local --become -v + && (echo 'Hostkeys test: pass' && exit 0) + || (echo 'Hostkeys test: fail' && exit 1) + + # Test 9: Test missing hostkeys + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_hostkeys_missing.yml --connection=local --become -v + && (echo 'Missing hostkeys test: pass' && exit 0) + || (echo 'Missing hostkeys test: fail' && exit 1) diff --git a/roles/sshd/.yamllint.yaml b/roles/sshd/.yamllint.yaml new file mode 100644 index 0000000..1708d26 --- /dev/null +++ b/roles/sshd/.yamllint.yaml @@ -0,0 +1,21 @@ +--- +# Based on ansible-lint config +extends: default + +rules: + braces: {max-spaces-inside: 1, level: error} + brackets: {max-spaces-inside: 1, level: error} + colons: {max-spaces-after: -1, level: error} + commas: {max-spaces-after: -1, level: error} + comments: disable + comments-indentation: disable + document-start: disable + empty-lines: {max: 3, level: error} + hyphens: {level: error} + indentation: disable + key-duplicates: enable + line-length: disable + new-line-at-end-of-file: disable + new-lines: {type: unix} + trailing-spaces: disable + truthy: disable diff --git a/roles/sshd/CHANGELOG b/roles/sshd/CHANGELOG new file mode 100644 index 0000000..b005f05 --- /dev/null +++ b/roles/sshd/CHANGELOG @@ -0,0 +1,27 @@ +0.2.5 23 January 2014 Matt Willsher +- Fix for sftp-server install on Debian removing openssh-sftp-server. Thanks to @ricbra +- Reinstate defaults.yml as fall through +0.2.4 13 January 2014 Matt Willsher +- Allow reload to be skipped +- Test for OS support +- Documentation improvements +0.2.3 13 January 2014 Matt Willsher +- Fixed HostbasedAuthentication typo +0.2.2 13 January 2014 Matt Willsher +- Add warnings to README +- Tidy up naming +- Remove blacklist packages from Debian based distros +0.2.1 12 January 2014 Matt Willsher +- Standardise README.md format +- Add basic Travis CI testing +- Add networking metadata type +0.2.0 04 January 2014 Matt Willsher +- Change var file search order +- Add Arch Linux defaults (thanks GitHub user @brenix). +- A number of typo fixes (again, thanks @brenix), including UsePrivilegeSeparation. +- A Ubuntu precise defaults. +- A Debian jessie defaults. +- Unknown Ubuntu and Debian versions default to wheezy defaults. +- License to LGPL +0.1.0 25 December 2014 Matt Willsher +- Initial release diff --git a/roles/sshd/CODE_OF_CONDUCT.md b/roles/sshd/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..6340074 --- /dev/null +++ b/roles/sshd/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at matt@willsher.systems. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/roles/sshd/LICENSE b/roles/sshd/LICENSE new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/roles/sshd/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/roles/sshd/README.md b/roles/sshd/README.md new file mode 100644 index 0000000..263d136 --- /dev/null +++ b/roles/sshd/README.md @@ -0,0 +1,299 @@ +OpenSSH Server +============== + +[![Build Status](https://travis-ci.org/willshersystems/ansible-sshd.svg?branch=master)](https://travis-ci.org/willshersystems/ansible-sshd) [![Ansible Galaxy](http://img.shields.io/badge/galaxy-willshersystems.sshd-660198.svg?style=flat)](https://galaxy.ansible.com/willshersystems/sshd/) + +This role configures the OpenSSH daemon. It: + +* By default configures the SSH daemon with the normal OS defaults. +* Works across a variety of `UN*X` distributions +* Can be configured by dict or simple variables +* Supports Match sets +* Supports all `sshd_config` options. Templates are programmatically generated. + (see [`meta/make_option_list`](meta/make_option_list)) +* Tests the `sshd_config` before reloading sshd. + +**WARNING** Misconfiguration of this role can lock you out of your server! +Please test your configuration and its interaction with your users configuration +before using in production! + +**WARNING** Digital Ocean allows root with passwords via SSH on Debian and +Ubuntu. This is not the default assigned by this module - it will set +`PermitRootLogin without-password` which will allow access via SSH key but not +via simple password. If you need this functionality, be sure to set +`sshd_PermitRootLogin yes` for those hosts. + +Requirements +------------ + +Tested on: + +* Ubuntu precise, trusty, xenial, bionic, focal +* Debian wheezy, jessie, stretch, buster +* FreeBSD 10.1 +* EL 6, 7, 8 derived distributions +* Fedora 31, 32, 33 +* OpenBSD 6.0 +* AIX 7.1, 7.2 + +It will likely work on other flavours and more direct support via suitable +[vars/](vars/) files is welcome. + +Role variables +--------------- + +Unconfigured, this role will provide a `sshd_config` that matches the OS default, +minus the comments and in a different order. + +* `sshd_enable` + +If set to *false*, the role will be completely disabled. Defaults to *true*. + +* `sshd_skip_defaults` + +If set to *true*, don't apply default values. This means that you must have a +complete set of configuration defaults via either the `sshd` dict, or +`sshd_Key` variables. Defaults to *false*. + +* `sshd_manage_service` + +If set to *false*, the service/daemon won't be **managed** at all, i.e. will not +try to enable on boot or start or reload the service. Defaults to *true* +unless: Running inside a docker container (it is assumed ansible is used during +build phase) or AIX (Ansible `service` module does not currently support `enabled` +for AIX) + +* `sshd_allow_reload` + +If set to *false*, a reload of sshd wont happen on change. This can help with +troubleshooting. You'll need to manually reload sshd if you want to apply the +changed configuration. Defaults to the same value as `sshd_manage_service`. +(Except on AIX, where `sshd_manage_service` is default *false*, but +`sshd_allow_reload` is default *true*) + +* `sshd_install_service` + +If set to *true*, the role will install service files for the ssh service. +Defaults to *false*. + +The templates for the service files to be used are pointed to by the variables + + - `sshd_service_template_service` (__default__: `templates/sshd.service.j2`) + - `sshd_service_template_at_service` (__default__: `templates/sshd@.service.j2`) + - `sshd_service_template_socket` (__default__: `templates/sshd.socket.j2`) + +Using these variables, you can use your own custom templates. With the above +default templates, the name of the installed ssh service will be provided by +the `sshd_service` variable. + +* `sshd` + +A dict containing configuration. e.g. + +```yaml +sshd: + Compression: delayed + ListenAddress: + - 0.0.0.0 +``` + +* `sshd_...` + +Simple variables can be used rather than a dict. Simple values override dict +values. e.g.: + +```yaml +sshd_Compression: off +``` + +In all cases, booleans are correctly rendered as yes and no in sshd +configuration. Lists can be used for multiline configuration items. e.g. + +```yaml +sshd_ListenAddress: + - 0.0.0.0 + - '::' +``` + +Renders as: + +``` +ListenAddress 0.0.0.0 +ListenAddress :: +``` + +* `sshd_match` + +A list of dicts for a match section. See the example playbook. + +* `sshd_match_1` through `sshd_match_9` + +A list of dicts or just a dict for a Match section. + +* `sshd_backup` + +When set to *false*, the original `sshd_config` file is not backed up. Default +is *true*. + +* `sshd_sysconfig` + +On RHEL-based systems, sysconfig is used for configuring more details of sshd +service. If set to *true*, this role will manage also the `/etc/sysconfig/sshd` +configuration file based on the following configuration. Default is *false*. + +* `sshd_sysconfig_override_crypto_policy` + +In RHEL8-based systems, this can be used to override system-wide crypto policy +by setting to *true*. Defaults to *false*. + +* `sshd_sysconfig_use_strong_rng` + +In RHEL-based systems, this can be used to force sshd to reseed openssl random +number generator with the given amount of bytes as an argument. The default is +*0*, which disables this functionality. It is not recommended to turn this on +if the system does not have hardware random number generator. + +* `sshd_config_file` + +The path where the openssh configuration produced by this role should be saved. +This is useful mostly when generating configuration snippets to Include. + +### Secondary role variables + +These variables are used by the role internals and can be used to override the +defaults that correspond to each supported platform. + +* `sshd_packages` + +Use this variable to override the default list of packages to install. + +* `sshd_config_owner`, `sshd_config_group`, `sshd_config_mode` + +Use these variables to set the ownership and permissions for the openssh config +file that this role produces. + +* `sshd_binary` + +The path to the openssh executable + +* `sshd_service` + +The name of the openssh service. By default, this variable contains the name of +the ssh service that the target platform uses. But it can also be used to set +the name of the custom ssh service when the `sshd_install_service` variable is +used. + +* `sshd_verify_hostkeys` + +By default (*auto*), this list contains all the host keys that are present in +the produced configuration file. The paths are checked for presence and +generated if missing. Additionally, permissions and file owners are set to sane +defaults. This is useful if the role is used in deployment stage to make sure +the service is able to start on the first attempt. To disable this check, set +this to empty list. + +* `sshd_hostkey_owner`, `sshd_hostkey_group`, `sshd_hostkey_group` + +Use these variables to set the ownership and permissions for the host keys from +the above list. + +* `sshd_sftp_server` + +Default path to the sftp server binary. + +Dependencies +------------ + +None + +Example Playbook +---------------- + +**DANGER!** This example is to show the range of configuration this role +provides. Running it will likely break your SSH access to the server! + +```yaml +--- +- hosts: all + vars: + sshd_skip_defaults: true + sshd: + Compression: true + ListenAddress: + - "0.0.0.0" + - "::" + GSSAPIAuthentication: no + Match: + - Condition: "Group user" + GSSAPIAuthentication: yes + sshd_UsePrivilegeSeparation: no + sshd_match: + - Condition: "Group xusers" + X11Forwarding: yes + roles: + - role: willshersystems.sshd +``` + +Results in: + +``` +# Ansible managed: ... +Compression yes +GSSAPIAuthentication no +UsePrivilegeSeparation no +Match Group user + GSSAPIAuthentication yes +Match Group xusers + X11Forwarding yes +``` + +Since Ansible 2.4, the role can be invoked using `include_role` keyword, +for example: + +```yaml +--- +- hosts: all + become: true + tasks: + - name: "Configure sshd" + include_role: + name: willshersystems.sshd + vars: + sshd_skip_defaults: true + sshd: + Compression: true + ListenAddress: + - "0.0.0.0" + - "::" + GSSAPIAuthentication: no + Match: + - Condition: "Group user" + GSSAPIAuthentication: yes + sshd_UsePrivilegeSeparation: no + sshd_match: + - Condition: "Group xusers" + X11Forwarding: yes +``` + +Template Generation +------------------- + +The [`sshd_config.j2`](templates/sshd_config.j2) template is programatically +generated by the scripts in meta. New options should be added to the +`options_body` or `options_match`. + +To regenerate the template, from within the meta/ directory run: +`./make_option_list >../templates/sshd_config.j2` + +License +------- + +LGPLv3 + + +Author +------ + +Matt Willsher + +© 2014,2015 Willsher Systems Ltd. diff --git a/roles/sshd/Vagrantfile b/roles/sshd/Vagrantfile new file mode 100644 index 0000000..f6eade3 --- /dev/null +++ b/roles/sshd/Vagrantfile @@ -0,0 +1,37 @@ + +# vi: set ft=ruby : + +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + + config.vm.synced_folder ".", "/vagrant", type: "nfs" + + config.vm.define "ubuntu" do |ubuntu| + ubuntu.vm.box = "boxcutter/ubuntu1604" + # ubuntu.vm.provision "shell", inline: <<-SHELL + # sudo add-apt-repository -y ppa:ansible/ansible + # sudo apt-get update -qq + # sudo apt-get -qq install ansible + # SHELL + end + + config.vm.define "centos7" do |centos| + centos.vm.box = "centos/7" + + centos.vm.provision "shell", inline: <<-SHELL + sudo yum install -y libselinux-python + SHELL + end + + config.vm.provision "shell", inline: <<-SHELL + test -e /vagrant/tests/roles/ansible-sshd || ln -s /vagrant /vagrant/tests/roles/ansible-sshd + SHELL + + config.vm.provision "ansible_local" do |ansible| +# ansible.config_file = "tests/ansible.cfg" + ansible.playbook = "tests/test.yml" + ansible.install = true + end + +end diff --git a/roles/sshd/defaults/main.yml b/roles/sshd/defaults/main.yml new file mode 100644 index 0000000..704dca3 --- /dev/null +++ b/roles/sshd/defaults/main.yml @@ -0,0 +1,72 @@ +--- +### USER OPTIONS +# Set to false to disable this role completely +sshd_enable: true + +# Don't apply OS defaults when set to true +sshd_skip_defaults: false + +# If the below is false, don't manage the service or reload the SSH +# daemon at all +sshd_manage_service: true + +# If the below is false, don't reload the ssh daemon on change +sshd_allow_reload: true + +# If the below is true, also install service files from the templates pointed +# to by the `sshd_service_template_*` variables +sshd_install_service: false +sshd_service_template_service: sshd.service.j2 +sshd_service_template_at_service: sshd@.service.j2 +sshd_service_template_socket: sshd.socket.j2 + +# If the below is true, create a backup of the config file when the template is copied +sshd_backup: true + +# If the below is true, also install the sysconfig file with the below options +# (useful only on Fedora and RHEL) +sshd_sysconfig: false + +# If the below is true the role will override also crypto policy configuration +sshd_sysconfig_override_crypto_policy: false + +# If the below is set to non-zero value, the OpenSSL random generator is +# reseeded with the given amount of random bytes (from getrandom(2) +# with GRND_RANDOM or /dev/random). Minimum is 14 bytes when enabled. +# This is not recommended to enable if you do not have hardware random number +# generator +sshd_sysconfig_use_strong_rng: 0 + + +# Empty dicts to avoid errors +sshd: {} + +# The path to sshd_config file. This is useful when creating an included +# configuration file snippet or configuring second sshd service +sshd_config_file: /etc/ssh/sshd_config + +### VARS DEFAULTS +### The following are defaults for OS specific configuration in var files in +### this role. They should not be set directly by role users. +sshd_packages: [] +sshd_config_owner: root +sshd_config_group: root +sshd_config_mode: "0600" +sshd_binary: /usr/sbin/sshd +sshd_service: sshd +sshd_sftp_server: /usr/lib/openssh/sftp-server + +# This lists by default all hostkeys as rendered in the generated configuration +# file ("auto"). Before attempting to run sshd (either for verification of +# configuration or restarting), we make sure the keys exist and have correct +# permissions. To disable this check, set sshd_verify_hostkeys to false +sshd_verify_hostkeys: "auto" +sshd_hostkey_owner: root +sshd_hostkey_group: root +sshd_hostkey_mode: "0600" + +### These variables are used by role internals and should not be used. +__sshd_defaults: {} +__sshd_os_supported: no +__sshd_sysconfig_supports_crypto_policy: false +__sshd_sysconfig_supports_use_strong_rng: false diff --git a/roles/sshd/handlers/main.yml b/roles/sshd/handlers/main.yml new file mode 100644 index 0000000..8c0eb7d --- /dev/null +++ b/roles/sshd/handlers/main.yml @@ -0,0 +1,27 @@ +--- + +- name: Reload the SSH service + service: + name: "{{ sshd_service }}" + state: reloaded + when: + - sshd_allow_reload|bool + - ansible_virtualization_type|default(None) != 'docker' + - ansible_virtualization_type|default(None) != 'VirtualPC' # for Github Actions + - ansible_connection != 'chroot' + - ansible_os_family != 'AIX' + listen: reload_sshd + +# sshd on AIX cannot be 'reloaded', it must be Stopped+Started. +# It's dangerous to do this in two tasks.. you're stopping SSH and then trying to SSH back in to start it. +# Instead, use a dirty shell script: +# https://www.ibm.com/developerworks/community/blogs/brian/entry/scripting_the_stop_and_restart_of_src_controlled_processes_on_aix6 +- name: Reload sshd Service (AIX) + shell: | + stopsrc -s sshd + until $(lssrc -s sshd | grep -q inoperative); do sleep 1; done + startsrc -s sshd + listen: reload_sshd + when: + - sshd_allow_reload|bool + - ansible_os_family == 'AIX' diff --git a/roles/sshd/meta/.galaxy_install_info b/roles/sshd/meta/.galaxy_install_info new file mode 100644 index 0000000..b3bfe48 --- /dev/null +++ b/roles/sshd/meta/.galaxy_install_info @@ -0,0 +1,2 @@ +install_date: Thu Mar 11 13:56:57 2021 +version: v0.12.0 diff --git a/roles/sshd/meta/10_top.j2 b/roles/sshd/meta/10_top.j2 new file mode 100644 index 0000000..040437b --- /dev/null +++ b/roles/sshd/meta/10_top.j2 @@ -0,0 +1,35 @@ +# {{ ansible_managed }} +{% macro render_option(key,value,indent=false) %} +{% if value is defined %} +{% if indent == true %} {% endif %} +{% if value is sameas true %} +{{ key }} yes +{% elif value is sameas false %} +{{ key }} no +{% elif value is string or value is number %} +{{ key }} {{ value }} +{% else %} +{% for i in value %} +{{ key }} {{ i }} +{% endfor %} +{% endif %} +{% endif %} +{% endmacro %} +{% macro body_option(key,override) %} +{% set value = undefined %} +{% if override is defined %} +{% set value = override %} +{% elif sshd[key] is defined %} +{% set value = sshd[key] %} +{% elif __sshd_defaults[key] is defined and sshd_skip_defaults != true %} +{% set value = __sshd_defaults[key] %} +{% endif %} +{{ render_option(key,value) -}} +{% endmacro %} +{% macro match_block(match_list) %} +{% if match_list["Condition"] is defined %} +{% set match_list = [ match_list ]%} +{% endif %} +{% if match_list is iterable %} +{% for match in match_list %} +Match {{ match["Condition"] }} diff --git a/roles/sshd/meta/20_middle.j2 b/roles/sshd/meta/20_middle.j2 new file mode 100644 index 0000000..a96e46a --- /dev/null +++ b/roles/sshd/meta/20_middle.j2 @@ -0,0 +1,3 @@ +{% endfor %} +{% endif %} +{% endmacro %} diff --git a/roles/sshd/meta/30_bottom.j2 b/roles/sshd/meta/30_bottom.j2 new file mode 100644 index 0000000..252ed85 --- /dev/null +++ b/roles/sshd/meta/30_bottom.j2 @@ -0,0 +1,33 @@ +{% if sshd['Match'] is defined %} +{{ match_block(sshd['Match']) -}} +{% endif %} +{% if sshd_match is defined %} +{{ match_block(sshd_match) -}} +{% endif %} +{% if sshd_match_1 is defined %} +{{ match_block(sshd_match_1) -}} +{% endif %} +{% if sshd_match_2 is defined %} +{{ match_block(sshd_match_2) -}} +{% endif %} +{% if sshd_match_3 is defined %} +{{ match_block(sshd_match_3) -}} +{% endif %} +{% if sshd_match_4 is defined %} +{{ match_block(sshd_match_4) -}} +{% endif %} +{% if sshd_match_5 is defined %} +{{ match_block(sshd_match_5) -}} +{% endif %} +{% if sshd_match_6 is defined %} +{{ match_block(sshd_match_6) -}} +{% endif %} +{% if sshd_match_7 is defined %} +{{ match_block(sshd_match_7) -}} +{% endif %} +{% if sshd_match_8 is defined %} +{{ match_block(sshd_match_8) -}} +{% endif %} +{% if sshd_match_9 is defined %} +{{ match_block(sshd_match_9) -}} +{% endif %} diff --git a/roles/sshd/meta/main.yml b/roles/sshd/meta/main.yml new file mode 100644 index 0000000..84344b2 --- /dev/null +++ b/roles/sshd/meta/main.yml @@ -0,0 +1,56 @@ +--- +galaxy_info: + author: Matt Willsher + description: OpenSSH SSH daemon configuration + company: Willsher Systems + license: LGPLv3 + min_ansible_version: 2.8 + platforms: + - name: Debian + versions: + - wheezy + - jessie + - stretch + - buster + - name: Ubuntu + versions: + - precise + - trusty + - xenial + - bionic + - focal + - name: FreeBSD + version: + - 10.1 + - name: EL + versions: + - 6 + - 7 + - 8 + - name: Fedora + versions: + - 31 + - 32 + - 33 + - name: OpenBSD + versions: + - 6.0 + - name: AIX + versions: + - 7.1 + - 7.2 + galaxy_tags: + - networking + - system + - ssh + - openssh + - sshd + - server + - ubuntu + - debian + - centos + - redhat + - freebsd + - openbsd + - aix +dependencies: [] diff --git a/roles/sshd/meta/make_option_list b/roles/sshd/meta/make_option_list new file mode 100755 index 0000000..b555093 --- /dev/null +++ b/roles/sshd/meta/make_option_list @@ -0,0 +1,16 @@ +#!/bin/sh +cat 10_top.j2 + +cat options_match | + awk '{ +print "{{ render_option(\""$1"\",match[\""$1"\"],true) -}}" +}' + +cat 20_middle.j2 + +cat options_body | + awk '{ +print "{{ body_option(\""$1"\",sshd_"$1") -}}" +}' + +cat 30_bottom.j2 diff --git a/roles/sshd/meta/options_body b/roles/sshd/meta/options_body new file mode 100644 index 0000000..ea65c0e --- /dev/null +++ b/roles/sshd/meta/options_body @@ -0,0 +1,107 @@ +Port +AddressFamily +ListenAddress +Protocol +HostKey +AcceptEnv +AllowAgentForwarding +AllowGroups +AllowStreamLocalForwarding +AllowTcpForwarding +AllowUsers +AuthenticationMethods +AuthorizedKeysCommand +AuthorizedKeysCommandUser +AuthorizedKeysFile +AuthorizedPrincipalsCommand +AuthorizedPrincipalsCommandUser +AuthorizedPrincipalsFile +Banner +CASignatureAlgorithms +ChallengeResponseAuthentication +ChrootDirectory +Ciphers +ClientAliveCountMax +ClientAliveInterval +Compression +DebianBanner +DenyGroups +DenyUsers +DisableForwarding +ExposeAuthInfo +FingerprintHash +ForceCommand +GatewayPorts +GSSAPIAuthentication +GSSAPICleanupCredentials +GSSAPIKeyExchange +GSSAPIKexAlgorithms +GSSAPIStoreCredentialsOnRekey +GSSAPIStrictAcceptorCheck +HPNBufferSize +HPNDisabled +HostCertificate +HostKeyAgent +HostKeyAlgorithms +HostbasedAcceptedKeyTypes +HostbasedAuthentication +HostbasedUsesNameFromPacketOnly +Include +IPQoS +IgnoreRhosts +IgnoreUserKnownHosts +KbdInteractiveAuthentication +KerberosAuthentication +KerberosGetAFSToken +KerberosOrLocalPasswd +KerberosTicketCleanup +KexAlgorithms +KeyRegenerationInterval +LogLevel +LoginGraceTime +MACs +MaxAuthTries +MaxSessions +MaxStartups +NoneEnabled +PasswordAuthentication +PermitEmptyPasswords +PermitListen +PermitOpen +PermitRootLogin +PermitTTY +PermitTunnel +PermitUserEnvironment +PermitUserRC +PidFile +PrintLastLog +PrintMotd +PubkeyAcceptedKeyTypes +PubkeyAuthOptions +PubkeyAuthentication +RSAAuthentication +RekeyLimit +RevokedKeys +RDomain +RhostsRSAAuthentication +SecurityKeyProvider +SetEnv +ServerKeyBits +StreamLocalBindMask +StreamLocalBindUnlink +StrictModes +Subsystem +SyslogFacility +TCPKeepAlive +TcpRcvBufPoll +TrustedUserCAKeys +UseDNS +UseLogin +UsePAM +UsePrivilegeSeparation +VersionAddendum +X11DisplayOffset +X11MaxDisplays +X11Forwarding +X11UseLocalhost +XAuthLocation diff --git a/roles/sshd/meta/options_match b/roles/sshd/meta/options_match new file mode 100644 index 0000000..e3f9dbe --- /dev/null +++ b/roles/sshd/meta/options_match @@ -0,0 +1,55 @@ +AcceptEnv +AllowAgentForwarding +AllowGroups +AllowStreamLocalForwarding +AllowTcpForwarding +AllowUsers +AuthenticationMethods +AuthorizedKeysCommand +AuthorizedKeysCommandUser +AuthorizedKeysFile +AuthorizedPrincipalsCommand +AuthorizedPrincipalsCommandUser +AuthorizedPrincipalsFile +Banner +ChrootDirectory +ClientAliveCountMax +ClientAliveInterval +DenyGroups +DenyUsers +ForceCommand +GatewayPorts +GSSAPIAuthentication +HostbasedAcceptedKeyTypes +HostbasedAuthentication +HostbasedUsesNameFromPacketOnly +Include +IPQoS +KbdInteractiveAuthentication +KerberosAuthentication +LogLevel +MaxAuthTries +MaxSessions +PasswordAuthentication +PermitEmptyPasswords +PermitListen +PermitOpen +PermitRootLogin +PermitTTY +PermitTunnel +PermitUserRC +PubkeyAcceptedKeyTypes +PubkeyAuthentication +RDomain +RekeyLimit +RevokedKeys +RhostsRSAAuthentication +RSAAuthentication +SetEnv +StreamLocalBindMask +StreamLocalBindUnlink +TrustedUserCAKeys +X11DisplayOffset +X11MaxDisplays +X11Forwarding +X11UseLocalHost diff --git a/roles/sshd/tasks/install.yml b/roles/sshd/tasks/install.yml new file mode 100644 index 0000000..2b7f6fd --- /dev/null +++ b/roles/sshd/tasks/install.yml @@ -0,0 +1,160 @@ +--- +- name: OS is supported + meta: end_host + when: + - not __sshd_os_supported|bool + +- name: Install ssh packages + package: + name: "{{ sshd_packages }}" + state: present + +- name: Sysconfig configuration + template: + src: sysconfig.j2 + dest: "/etc/sysconfig/sshd" + owner: "root" + group: "root" + mode: "600" + backup: "{{ sshd_backup }}" + when: + - sshd_sysconfig|bool + notify: reload_sshd + +- name: Make sure hostkeys are available and have expected permissions + vars: &share_vars + # This mimics the macro body_option() in sshd_config.j2 + # The explicit to_json filter is needed for Python 2 compatibility + __sshd_hostkeys_from_config: >- + {% if sshd_HostKey is defined %} + {{ sshd_HostKey | to_json }} + {% elif sshd['HostKey'] is defined %} + {{ sshd['HostKey'] | to_json }} + {% elif __sshd_defaults['HostKey'] is defined and not sshd_skip_defaults %} + {{ __sshd_defaults['HostKey'] | to_json }} + {% else %} + [] + {% endif %} + __sshd_verify_hostkeys: >- + {% if not sshd_verify_hostkeys %} + [] + {% elif sshd_verify_hostkeys == 'auto' %} + {{ __sshd_hostkeys_from_config }} + {% else %} + {{ sshd_verify_hostkeys | to_json }} + {% endif %} + block: + - name: Make sure hostkeys are available + shell: > + {% if sshd_sysconfig %} + source /etc/sysconfig/sshd; + {% endif %} + ssh-keygen -q -t {{ item | regex_search('(rsa|dsa|ecdsa|ed25519)') }} -f {{ item }} -C '' -N '' + args: + creates: "{{ item }}" + loop: "{{ __sshd_verify_hostkeys | from_json | list }}" + + - name: Make sure private hostkeys have expected permissions + file: + path: "{{ item }}" + owner: "{{ sshd_hostkey_owner }}" + group: "{{ sshd_hostkey_group }}" + mode: "{{ sshd_hostkey_mode }}" + loop: "{{ __sshd_verify_hostkeys | from_json | list }}" + +- name: Apply configuration + vars: + <<: *share_vars + block: + - name: Create a temporary hostkey for syntax verification if needed + tempfile: + state: directory + register: sshd_test_hostkey + changed_when: False + when: + - __sshd_hostkeys_from_config | from_json == [] + - sshd_config_file != "/etc/ssh/sshd_config" + + - name: Generate temporary hostkey + shell: "ssh-keygen -q -t rsa -f {{ sshd_test_hostkey.path }}/rsa_key -C '' -N ''" + changed_when: False + when: sshd_test_hostkey.path is defined + + - name: Create the configuration file + template: + src: sshd_config.j2 + dest: "{{ sshd_config_file }}" + owner: "{{ sshd_config_owner }}" + group: "{{ sshd_config_group }}" + mode: "{{ sshd_config_mode }}" + validate: >- + {% if sshd_test_hostkey is defined and sshd_test_hostkey.path is defined %} + {{ sshd_binary }} -t -f %s -h {{ sshd_test_hostkey.path }}/rsa_key + {% else %} + {{ sshd_binary }} -t -f %s + {% endif %} + backup: "{{ sshd_backup }}" + notify: reload_sshd + rescue: + - name: re-raise the error + fail: + msg: "{{ ansible_failed_result }}" + always: + - name: Remove temporary host keys + file: + path: "{{ sshd_test_hostkey.path }}" + state: absent + changed_when: False + when: sshd_test_hostkey.path is defined + +- name: Install systemd service files + block: + - name: Install service unit file + template: + src: "{{ sshd_service_template_service }}" + dest: "/etc/systemd/system/{{ sshd_service }}.service" + owner: root + group: root + mode: "0644" + notify: reload_sshd + - name: Install instanced service unit file + template: + src: "{{ sshd_service_template_at_service }}" + dest: "/etc/systemd/system/{{ sshd_service }}@.service" + owner: root + group: root + mode: "0644" + notify: reload_sshd + - name: Install socket unit file + template: + src: "{{ sshd_service_template_socket }}" + dest: "/etc/systemd/system/{{ sshd_service }}.socket" + owner: root + group: root + mode: "0644" + notify: reload_sshd + when: sshd_install_service|bool + +- name: Service enabled and running + service: + name: "{{ sshd_service }}" + enabled: true + state: started + when: + - sshd_manage_service|bool + - ansible_virtualization_type|default(None) != 'docker' + - ansible_virtualization_type|default(None) != 'VirtualPC' # for Github Actions + - ansible_connection != 'chroot' + +# Due to ansible bug 21026, cannot use service module on RHEL 7 +- name: Enable service in chroot + command: systemctl enable {{ sshd_service }} # noqa 303 + when: + - ansible_connection == 'chroot' + - ansible_os_family == 'RedHat' + - ansible_distribution_major_version|int >= 7 + +- name: Register that this role has run + set_fact: + sshd_has_run: true + when: sshd_has_run is not defined diff --git a/roles/sshd/tasks/main.yml b/roles/sshd/tasks/main.yml new file mode 100644 index 0000000..54b708e --- /dev/null +++ b/roles/sshd/tasks/main.yml @@ -0,0 +1,4 @@ +--- + +- include_tasks: sshd.yml + when: sshd_enable|bool diff --git a/roles/sshd/tasks/sshd.yml b/roles/sshd/tasks/sshd.yml new file mode 100644 index 0000000..57cb12b --- /dev/null +++ b/roles/sshd/tasks/sshd.yml @@ -0,0 +1,5 @@ +--- + +- include_tasks: variables.yml + +- include_tasks: install.yml diff --git a/roles/sshd/tasks/variables.yml b/roles/sshd/tasks/variables.yml new file mode 100644 index 0000000..9d9aa1d --- /dev/null +++ b/roles/sshd/tasks/variables.yml @@ -0,0 +1,27 @@ +--- +- name: Set OS dependent variables + include_vars: "{{ lookup('first_found', params) }}" + vars: + ansible_distribution_lts_offset: >- + {{ + ansible_distribution_major_version|int % 2 + if ansible_distribution == "Ubuntu" + else 0 + }} + ansible_distribution_lts_version: >- + {{ + ansible_distribution_major_version|int - + ansible_distribution_lts_offset|int + if ansible_distribution == "Ubuntu" + else ansible_distribution_version + }} + params: + files: + - "{{ ansible_distribution }}_{{ ansible_distribution_lts_version }}.yml" + - "{{ ansible_distribution }}.yml" + - "{{ ansible_os_family }}_{{ ansible_distribution_major_version }}.yml" + - "{{ ansible_os_family }}.yml" + - default.yml + paths: + - "{{ role_path }}/vars" + - "{{ playbook_dir }}/vars" diff --git a/roles/sshd/templates/sshd.service.j2 b/roles/sshd/templates/sshd.service.j2 new file mode 100644 index 0000000..a969ebb --- /dev/null +++ b/roles/sshd/templates/sshd.service.j2 @@ -0,0 +1,17 @@ +[Unit] +Description=OpenBSD Secure Shell server + +[Service] +ExecStartPre={{ sshd_binary }} -t +ExecStart={{ sshd_binary }} -D -f {{ sshd_config_file }} +ExecReload={{ sshd_binary }} -t +ExecReload=/bin/kill -HUP $MAINPID +KillMode=process +Restart=on-failure +RestartPreventExitStatus=255 +Type=notify +RuntimeDirectory={{ sshd_binary | basename }} +RuntimeDirectoryMode=0755 + +[Install] +WantedBy=multi-user.target diff --git a/roles/sshd/templates/sshd.socket.j2 b/roles/sshd/templates/sshd.socket.j2 new file mode 100644 index 0000000..add4731 --- /dev/null +++ b/roles/sshd/templates/sshd.socket.j2 @@ -0,0 +1,11 @@ +[Unit] +Description=OpenBSD Secure Shell server socket +Before={{ sshd_service }}.service +Conflicts={{sshd_service }}.service + +[Socket] +ListenStream=22 +Accept=yes + +[Install] +WantedBy=sockets.target diff --git a/roles/sshd/templates/sshd@.service.j2 b/roles/sshd/templates/sshd@.service.j2 new file mode 100644 index 0000000..d76fdde --- /dev/null +++ b/roles/sshd/templates/sshd@.service.j2 @@ -0,0 +1,9 @@ +[Unit] +Description=OpenBSD Secure Shell server per-connection daemon +After=auditd.service + +[Service] +ExecStart=-{{ sshd_binary }} -i -f {{ sshd_config_file }} +StandardInput=socket +RuntimeDirectory={{ sshd_binary }} +RuntimeDirectoryMode=0755 diff --git a/roles/sshd/templates/sshd_config.j2 b/roles/sshd/templates/sshd_config.j2 new file mode 100644 index 0000000..6f61832 --- /dev/null +++ b/roles/sshd/templates/sshd_config.j2 @@ -0,0 +1,242 @@ +# {{ ansible_managed }} +{% macro render_option(key,value,indent=false) %} +{% if value is defined %} +{% if indent == true %} {% endif %} +{% if value is sameas true %} +{{ key }} yes +{% elif value is sameas false %} +{{ key }} no +{% elif value is string or value is number %} +{{ key }} {{ value }} +{% else %} +{% for i in value %} +{{ key }} {{ i }} +{% endfor %} +{% endif %} +{% endif %} +{% endmacro %} +{% macro body_option(key,override) %} +{% set value = undefined %} +{% if override is defined %} +{% set value = override %} +{% elif sshd[key] is defined %} +{% set value = sshd[key] %} +{% elif __sshd_defaults[key] is defined and sshd_skip_defaults != true %} +{% set value = __sshd_defaults[key] %} +{% endif %} +{{ render_option(key,value) -}} +{% endmacro %} +{% macro match_block(match_list) %} +{% if match_list["Condition"] is defined %} +{% set match_list = [ match_list ]%} +{% endif %} +{% if match_list is iterable %} +{% for match in match_list %} +Match {{ match["Condition"] }} +{{ render_option("AcceptEnv",match["AcceptEnv"],true) -}} +{{ render_option("AllowAgentForwarding",match["AllowAgentForwarding"],true) -}} +{{ render_option("AllowGroups",match["AllowGroups"],true) -}} +{{ render_option("AllowStreamLocalForwarding",match["AllowStreamLocalForwarding"],true) -}} +{{ render_option("AllowTcpForwarding",match["AllowTcpForwarding"],true) -}} +{{ render_option("AllowUsers",match["AllowUsers"],true) -}} +{{ render_option("AuthenticationMethods",match["AuthenticationMethods"],true) -}} +{{ render_option("AuthorizedKeysCommand",match["AuthorizedKeysCommand"],true) -}} +{{ render_option("AuthorizedKeysCommandUser",match["AuthorizedKeysCommandUser"],true) -}} +{{ render_option("AuthorizedKeysFile",match["AuthorizedKeysFile"],true) -}} +{{ render_option("AuthorizedPrincipalsCommand",match["AuthorizedPrincipalsCommand"],true) -}} +{{ render_option("AuthorizedPrincipalsCommandUser",match["AuthorizedPrincipalsCommandUser"],true) -}} +{{ render_option("AuthorizedPrincipalsFile",match["AuthorizedPrincipalsFile"],true) -}} +{{ render_option("Banner",match["Banner"],true) -}} +{{ render_option("ChrootDirectory",match["ChrootDirectory"],true) -}} +{{ render_option("ClientAliveCountMax",match["ClientAliveCountMax"],true) -}} +{{ render_option("ClientAliveInterval",match["ClientAliveInterval"],true) -}} +{{ render_option("DenyGroups",match["DenyGroups"],true) -}} +{{ render_option("DenyUsers",match["DenyUsers"],true) -}} +{{ render_option("ForceCommand",match["ForceCommand"],true) -}} +{{ render_option("GatewayPorts",match["GatewayPorts"],true) -}} +{{ render_option("GSSAPIAuthentication",match["GSSAPIAuthentication"],true) -}} +{{ render_option("HostbasedAcceptedKeyTypes",match["HostbasedAcceptedKeyTypes"],true) -}} +{{ render_option("HostbasedAuthentication",match["HostbasedAuthentication"],true) -}} +{{ render_option("HostbasedUsesNameFromPacketOnly",match["HostbasedUsesNameFromPacketOnly"],true) -}} +{{ render_option("Include",match["Include"],true) -}} +{{ render_option("IPQoS",match["IPQoS"],true) -}} +{{ render_option("KbdInteractiveAuthentication",match["KbdInteractiveAuthentication"],true) -}} +{{ render_option("KerberosAuthentication",match["KerberosAuthentication"],true) -}} +{{ render_option("LogLevel",match["LogLevel"],true) -}} +{{ render_option("MaxAuthTries",match["MaxAuthTries"],true) -}} +{{ render_option("MaxSessions",match["MaxSessions"],true) -}} +{{ render_option("PasswordAuthentication",match["PasswordAuthentication"],true) -}} +{{ render_option("PermitEmptyPasswords",match["PermitEmptyPasswords"],true) -}} +{{ render_option("PermitListen",match["PermitListen"],true) -}} +{{ render_option("PermitOpen",match["PermitOpen"],true) -}} +{{ render_option("PermitRootLogin",match["PermitRootLogin"],true) -}} +{{ render_option("PermitTTY",match["PermitTTY"],true) -}} +{{ render_option("PermitTunnel",match["PermitTunnel"],true) -}} +{{ render_option("PermitUserRC",match["PermitUserRC"],true) -}} +{{ render_option("PubkeyAcceptedKeyTypes",match["PubkeyAcceptedKeyTypes"],true) -}} +{{ render_option("PubkeyAuthentication",match["PubkeyAuthentication"],true) -}} +{{ render_option("RDomain",match["RDomain"],true) -}} +{{ render_option("RekeyLimit",match["RekeyLimit"],true) -}} +{{ render_option("RevokedKeys",match["RevokedKeys"],true) -}} +{{ render_option("RhostsRSAAuthentication",match["RhostsRSAAuthentication"],true) -}} +{{ render_option("RSAAuthentication",match["RSAAuthentication"],true) -}} +{{ render_option("SetEnv",match["SetEnv"],true) -}} +{{ render_option("StreamLocalBindMask",match["StreamLocalBindMask"],true) -}} +{{ render_option("StreamLocalBindUnlink",match["StreamLocalBindUnlink"],true) -}} +{{ render_option("TrustedUserCAKeys",match["TrustedUserCAKeys"],true) -}} +{{ render_option("X11DisplayOffset",match["X11DisplayOffset"],true) -}} +{{ render_option("X11MaxDisplays",match["X11MaxDisplays"],true) -}} +{{ render_option("X11Forwarding",match["X11Forwarding"],true) -}} +{{ render_option("X11UseLocalHost",match["X11UseLocalHost"],true) -}} +{% endfor %} +{% endif %} +{% endmacro %} +{% macro match_iterate_block(match_list) %} +{% if match_list | type_debug == "list" %} +{% for match in match_list %} +{{ match_block(match) -}} +{% endfor %} +{% else %} +{{ match_block(match_list) -}} +{% endif %} +{% endmacro %} +{{ body_option("Port",sshd_Port) -}} +{{ body_option("AddressFamily",sshd_AddressFamily) -}} +{{ body_option("ListenAddress",sshd_ListenAddress) -}} +{{ body_option("Protocol",sshd_Protocol) -}} +{{ body_option("HostKey",sshd_HostKey) -}} +{{ body_option("AcceptEnv",sshd_AcceptEnv) -}} +{{ body_option("AllowAgentForwarding",sshd_AllowAgentForwarding) -}} +{{ body_option("AllowGroups",sshd_AllowGroups) -}} +{{ body_option("AllowStreamLocalForwarding",sshd_AllowStreamLocalForwarding) -}} +{{ body_option("AllowTcpForwarding",sshd_AllowTcpForwarding) -}} +{{ body_option("AllowUsers",sshd_AllowUsers) -}} +{{ body_option("AuthenticationMethods",sshd_AuthenticationMethods) -}} +{{ body_option("AuthorizedKeysCommand",sshd_AuthorizedKeysCommand) -}} +{{ body_option("AuthorizedKeysCommandUser",sshd_AuthorizedKeysCommandUser) -}} +{{ body_option("AuthorizedKeysFile",sshd_AuthorizedKeysFile) -}} +{{ body_option("AuthorizedPrincipalsCommand",sshd_AuthorizedPrincipalsCommand) -}} +{{ body_option("AuthorizedPrincipalsCommandUser",sshd_AuthorizedPrincipalsCommandUser) -}} +{{ body_option("AuthorizedPrincipalsFile",sshd_AuthorizedPrincipalsFile) -}} +{{ body_option("Banner",sshd_Banner) -}} +{{ body_option("CASignatureAlgorithms",sshd_CASignatureAlgorithms) -}} +{{ body_option("ChallengeResponseAuthentication",sshd_ChallengeResponseAuthentication) -}} +{{ body_option("ChrootDirectory",sshd_ChrootDirectory) -}} +{{ body_option("Ciphers",sshd_Ciphers) -}} +{{ body_option("ClientAliveCountMax",sshd_ClientAliveCountMax) -}} +{{ body_option("ClientAliveInterval",sshd_ClientAliveInterval) -}} +{{ body_option("Compression",sshd_Compression) -}} +{{ body_option("DebianBanner",sshd_DebianBanner) -}} +{{ body_option("DenyGroups",sshd_DenyGroups) -}} +{{ body_option("DenyUsers",sshd_DenyUsers) -}} +{{ body_option("DisableForwarding",sshd_DisableForwarding) -}} +{{ body_option("ExposeAuthInfo",sshd_ExposeAuthInfo) -}} +{{ body_option("FingerprintHash",sshd_FingerprintHash) -}} +{{ body_option("ForceCommand",sshd_ForceCommand) -}} +{{ body_option("GatewayPorts",sshd_GatewayPorts) -}} +{{ body_option("GSSAPIAuthentication",sshd_GSSAPIAuthentication) -}} +{{ body_option("GSSAPICleanupCredentials",sshd_GSSAPICleanupCredentials) -}} +{{ body_option("GSSAPIKeyExchange",sshd_GSSAPIKeyExchange) -}} +{{ body_option("GSSAPIKexAlgorithms",sshd_GSSAPIKexAlgorithms) -}} +{{ body_option("GSSAPIStoreCredentialsOnRekey",sshd_GSSAPIStoreCredentialsOnRekey) -}} +{{ body_option("GSSAPIStrictAcceptorCheck",sshd_GSSAPIStrictAcceptorCheck) -}} +{{ body_option("HPNBufferSize",sshd_HPNBufferSize) -}} +{{ body_option("HPNDisabled",sshd_HPNDisabled) -}} +{{ body_option("HostCertificate",sshd_HostCertificate) -}} +{{ body_option("HostKeyAgent",sshd_HostKeyAgent) -}} +{{ body_option("HostKeyAlgorithms",sshd_HostKeyAlgorithms) -}} +{{ body_option("HostbasedAcceptedKeyTypes",sshd_HostbasedAcceptedKeyTypes) -}} +{{ body_option("HostbasedAuthentication",sshd_HostbasedAuthentication) -}} +{{ body_option("HostbasedUsesNameFromPacketOnly",sshd_HostbasedUsesNameFromPacketOnly) -}} +{{ body_option("Include",sshd_Include) -}} +{{ body_option("IPQoS",sshd_IPQoS) -}} +{{ body_option("IgnoreRhosts",sshd_IgnoreRhosts) -}} +{{ body_option("IgnoreUserKnownHosts",sshd_IgnoreUserKnownHosts) -}} +{{ body_option("KbdInteractiveAuthentication",sshd_KbdInteractiveAuthentication) -}} +{{ body_option("KerberosAuthentication",sshd_KerberosAuthentication) -}} +{{ body_option("KerberosGetAFSToken",sshd_KerberosGetAFSToken) -}} +{{ body_option("KerberosOrLocalPasswd",sshd_KerberosOrLocalPasswd) -}} +{{ body_option("KerberosTicketCleanup",sshd_KerberosTicketCleanup) -}} +{{ body_option("KexAlgorithms",sshd_KexAlgorithms) -}} +{{ body_option("KeyRegenerationInterval",sshd_KeyRegenerationInterval) -}} +{{ body_option("LogLevel",sshd_LogLevel) -}} +{{ body_option("LoginGraceTime",sshd_LoginGraceTime) -}} +{{ body_option("MACs",sshd_MACs) -}} +{{ body_option("MaxAuthTries",sshd_MaxAuthTries) -}} +{{ body_option("MaxSessions",sshd_MaxSessions) -}} +{{ body_option("MaxStartups",sshd_MaxStartups) -}} +{{ body_option("NoneEnabled",sshd_NoneEnabled) -}} +{{ body_option("PasswordAuthentication",sshd_PasswordAuthentication) -}} +{{ body_option("PermitEmptyPasswords",sshd_PermitEmptyPasswords) -}} +{{ body_option("PermitListen",sshd_PermitListen) -}} +{{ body_option("PermitOpen",sshd_PermitOpen) -}} +{{ body_option("PermitRootLogin",sshd_PermitRootLogin) -}} +{{ body_option("PermitTTY",sshd_PermitTTY) -}} +{{ body_option("PermitTunnel",sshd_PermitTunnel) -}} +{{ body_option("PermitUserEnvironment",sshd_PermitUserEnvironment) -}} +{{ body_option("PermitUserRC",sshd_PermitUserRC) -}} +{{ body_option("PidFile",sshd_PidFile) -}} +{{ body_option("PrintLastLog",sshd_PrintLastLog) -}} +{{ body_option("PrintMotd",sshd_PrintMotd) -}} +{{ body_option("PubkeyAcceptedKeyTypes",sshd_PubkeyAcceptedKeyTypes) -}} +{{ body_option("PubkeyAuthOptions",sshd_PubkeyAuthOptions) -}} +{{ body_option("PubkeyAuthentication",sshd_PubkeyAuthentication) -}} +{{ body_option("RSAAuthentication",sshd_RSAAuthentication) -}} +{{ body_option("RekeyLimit",sshd_RekeyLimit) -}} +{{ body_option("RevokedKeys",sshd_RevokedKeys) -}} +{{ body_option("RDomain",sshd_RDomain) -}} +{{ body_option("RhostsRSAAuthentication",sshd_RhostsRSAAuthentication) -}} +{{ body_option("SecurityKeyProvider",sshd_SecurityKeyProvider) -}} +{{ body_option("SetEnv",sshd_SetEnv) -}} +{{ body_option("ServerKeyBits",sshd_ServerKeyBits) -}} +{{ body_option("StreamLocalBindMask",sshd_StreamLocalBindMask) -}} +{{ body_option("StreamLocalBindUnlink",sshd_StreamLocalBindUnlink) -}} +{{ body_option("StrictModes",sshd_StrictModes) -}} +{{ body_option("Subsystem",sshd_Subsystem) -}} +{{ body_option("SyslogFacility",sshd_SyslogFacility) -}} +{{ body_option("TCPKeepAlive",sshd_TCPKeepAlive) -}} +{{ body_option("TcpRcvBufPoll",sshd_TcpRcvBufPoll) -}} +{{ body_option("TrustedUserCAKeys",sshd_TrustedUserCAKeys) -}} +{{ body_option("UseDNS",sshd_UseDNS) -}} +{{ body_option("UseLogin",sshd_UseLogin) -}} +{{ body_option("UsePAM",sshd_UsePAM) -}} +{{ body_option("UsePrivilegeSeparation",sshd_UsePrivilegeSeparation) -}} +{{ body_option("VersionAddendum",sshd_VersionAddendum) -}} +{{ body_option("X11DisplayOffset",sshd_X11DisplayOffset) -}} +{{ body_option("X11MaxDisplays",sshd_X11MaxDisplays) -}} +{{ body_option("X11Forwarding",sshd_X11Forwarding) -}} +{{ body_option("X11UseLocalhost",sshd_X11UseLocalhost) -}} +{{ body_option("XAuthLocation",sshd_XAuthLocation) -}} +{% if sshd['Match'] is defined %} +{{ match_iterate_block(sshd['Match']) -}} +{% endif %} +{% if sshd_match is defined %} +{{ match_iterate_block(sshd_match) -}} +{% endif %} +{% if sshd_match_1 is defined %} +{{ match_block(sshd_match_1) -}} +{% endif %} +{% if sshd_match_2 is defined %} +{{ match_block(sshd_match_2) -}} +{% endif %} +{% if sshd_match_3 is defined %} +{{ match_block(sshd_match_3) -}} +{% endif %} +{% if sshd_match_4 is defined %} +{{ match_block(sshd_match_4) -}} +{% endif %} +{% if sshd_match_5 is defined %} +{{ match_block(sshd_match_5) -}} +{% endif %} +{% if sshd_match_6 is defined %} +{{ match_block(sshd_match_6) -}} +{% endif %} +{% if sshd_match_7 is defined %} +{{ match_block(sshd_match_7) -}} +{% endif %} +{% if sshd_match_8 is defined %} +{{ match_block(sshd_match_8) -}} +{% endif %} +{% if sshd_match_9 is defined %} +{{ match_block(sshd_match_9) -}} +{% endif %} diff --git a/roles/sshd/templates/sysconfig.j2 b/roles/sshd/templates/sysconfig.j2 new file mode 100644 index 0000000..045d61c --- /dev/null +++ b/roles/sshd/templates/sysconfig.j2 @@ -0,0 +1,10 @@ +# {{ ansible_managed }} +{% if __sshd_sysconfig_supports_crypto_policy %} +{% if sshd_sysconfig_override_crypto_policy == true %} +CRYPTO_POLICY= +{% endif %} +{% endif %} + +{% if __sshd_sysconfig_supports_use_strong_rng %} +SSH_USE_STRONG_RNG={{ sshd_sysconfig_use_strong_rng }} +{% endif %} diff --git a/roles/sshd/tests/inventory b/roles/sshd/tests/inventory new file mode 100644 index 0000000..2fbb50c --- /dev/null +++ b/roles/sshd/tests/inventory @@ -0,0 +1 @@ +localhost diff --git a/roles/sshd/tests/roles/.gitkeep b/roles/sshd/tests/roles/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/sshd/tests/roles/ansible-sshd b/roles/sshd/tests/roles/ansible-sshd new file mode 120000 index 0000000..6581736 --- /dev/null +++ b/roles/sshd/tests/roles/ansible-sshd @@ -0,0 +1 @@ +../../ \ No newline at end of file diff --git a/roles/sshd/tests/tests_alternative_file.yml b/roles/sshd/tests/tests_alternative_file.yml new file mode 100644 index 0000000..0272fce --- /dev/null +++ b/roles/sshd/tests/tests_alternative_file.yml @@ -0,0 +1,92 @@ +--- +- hosts: all + tasks: + - name: Configure alternative sshd_config file + include_role: + name: ansible-sshd + vars: + # just anything -- will not get processed by sshd + sshd_config_file: /etc/ssh/sshd_config_custom + sshd_skip_defaults: true + sshd: + AcceptEnv: LANG + Banner: /etc/issue + Ciphers: aes256-gcm@openssh.com + sshd_Compression: no + - name: Configure second alternative sshd_config file + include_role: + name: ansible-sshd + vars: + # just anything -- will not get processed by sshd + sshd_config_file: /etc/ssh/sshd_config_custom_second + sshd_skip_defaults: true + sshd: + Banner: /etc/issue2 + Ciphers: aes128-gcm@openssh.com + sshd_MaxStartups: 100 + - name: Now configure the main sshd_config file + include_role: + name: ansible-sshd + vars: + sshd: + Banner: /etc/issue + Ciphers: aes128-ctr + HostKey: + - /tmp/ssh_host_ecdsa_key + sshd_PasswordAuthentication: no + + - name: Verify the options are correctly set + vars: + main_sshd_config: >- + {{ + "/etc/ssh/sshd_config.d/00-ansible_system_role.conf" + if ansible_facts['distribution'] == 'Fedora' + else "/etc/ssh/sshd_config" + }} + block: + - meta: flush_handlers + + - name: Print current configuration file + slurp: + src: /etc/ssh/sshd_config_custom + register: config + + - name: Print second configuration file + slurp: + src: /etc/ssh/sshd_config_custom_second + register: config2 + + - name: Print the main configuration file + slurp: + src: "{{ main_sshd_config }}" + register: config3 + + - name: Check content of first configuration file + assert: + that: + - "'AcceptEnv LANG' in config.content | b64decode" + - "'Banner /etc/issue' in config.content | b64decode" + - "'Ciphers aes256-gcm@openssh.com' in config.content | b64decode" + - "'HostKey' not in config.content | b64decode" + - "'Compression no' in config.content | b64decode" + - "'MaxStartups 100' not in config.content | b64decode" + + - name: Check content of second configuration file + assert: + that: + - "'Banner /etc/issue2' in config2.content | b64decode" + - "'Ciphers aes128-gcm@openssh.com' in config2.content | b64decode" + - "'HostKey' not in config2.content | b64decode" + - "'MaxStartups 100' in config2.content | b64decode" + - "'Compression no' not in config2.content | b64decode" + + - name: Check content of the main configuration file + assert: + that: + - "'Banner /etc/issue' in config3.content | b64decode" + - "'Ciphers aes128-ctr' in config3.content | b64decode" + - "'HostKey /tmp/ssh_host_ecdsa_key' in config3.content | b64decode" + - "'PasswordAuthentication no' in config3.content | b64decode" + - "'MaxStartups 100' not in config3.content | b64decode" + - "'Compression no' not in config3.content | b64decode" + tags: tests::verify diff --git a/roles/sshd/tests/tests_default.yml b/roles/sshd/tests/tests_default.yml new file mode 100644 index 0000000..dfb0f89 --- /dev/null +++ b/roles/sshd/tests/tests_default.yml @@ -0,0 +1,4 @@ +--- +- hosts: all + roles: + - ansible-sshd diff --git a/roles/sshd/tests/tests_default_include.yml b/roles/sshd/tests/tests_default_include.yml new file mode 100644 index 0000000..d3d98d7 --- /dev/null +++ b/roles/sshd/tests/tests_default_include.yml @@ -0,0 +1,6 @@ +--- +- hosts: all + tasks: + - name: "Configure sshd" + include_role: + name: ansible-sshd diff --git a/roles/sshd/tests/tests_hostkeys.yml b/roles/sshd/tests/tests_hostkeys.yml new file mode 100644 index 0000000..2e73538 --- /dev/null +++ b/roles/sshd/tests/tests_hostkeys.yml @@ -0,0 +1,70 @@ +--- +- hosts: all + tasks: + - name: Remove host key before the test + file: + path: /tmp/ssh_host_ed25519_key + state: absent + + - name: Ensure group 'nobody' exists + group: + name: nobody + + - name: Ensure the user 'nobody' exists + user: + name: nobody + group: nobody + comment: nobody + create_home: no + shell: /sbin/nologin + + - name: Configure sshd with alternative host keys + include_role: + name: ansible-sshd + vars: + # very BAD example + sshd_hostkey_owner: "nobody" + sshd_hostkey_group: "nobody" + sshd_hostkey_mode: "0664" + sshd: + HostKey: + - /tmp/ssh_host_ed25519_key + + - name: Verify the options are correctly set + vars: + main_sshd_config: >- + {{ + "/etc/ssh/sshd_config.d/00-ansible_system_role.conf" + if ansible_facts['distribution'] == 'Fedora' + else "/etc/ssh/sshd_config" + }} + block: + - meta: flush_handlers + + - name: Print current configuration file + slurp: + src: "{{ main_sshd_config }}" + register: config + + - stat: + path: /tmp/ssh_host_ed25519_key + register: privkey + + - stat: + path: /tmp/ssh_host_ed25519_key.pub + register: pubkey + + - name: Check the options are in configuration file + assert: + that: + - "'HostKey /tmp/ssh_host_ed25519_key' in config.content | b64decode" + + - name: Check the generated host key has requested properties + assert: + that: + - privkey.stat.exists + - privkey.stat.gr_name == 'nobody' + - privkey.stat.pw_name == 'nobody' + - privkey.stat.mode == '0664' + - pubkey.stat.exists + tags: tests::verify diff --git a/roles/sshd/tests/tests_hostkeys_missing.yml b/roles/sshd/tests/tests_hostkeys_missing.yml new file mode 100644 index 0000000..513ee19 --- /dev/null +++ b/roles/sshd/tests/tests_hostkeys_missing.yml @@ -0,0 +1,33 @@ +--- +- hosts: all + tasks: + - name: Configure sshd with missing host keys and prevent their creation + block: + - name: Configure missing hostkey + include_role: + name: ansible-sshd + vars: + sshd_verify_hostkeys: [] + sshd: + HostKey: + - /tmp/missing_ssh_host_rsa_key + register: role_result + + - name: unreachable task + fail: + msg: UNREACH + + rescue: + - name: Check that we failed in the role + assert: + that: + - ansible_failed_result.msg != 'UNREACH' + - not role_result.changed + msg: "Role has not failed when it should have" + + - name: Make sure service is still running + service: + name: sshd + state: started + register: result + failed_when: result.changed diff --git a/roles/sshd/tests/tests_match.yml b/roles/sshd/tests/tests_match.yml new file mode 100644 index 0000000..829e628 --- /dev/null +++ b/roles/sshd/tests/tests_match.yml @@ -0,0 +1,81 @@ +--- +- hosts: all + tasks: + - name: Configure sshd + include_role: + name: ansible-sshd + vars: + # For Fedora containers, we need to make sure we have keys for sshd -T below + sshd_verify_hostkeys: + - /etc/ssh/ssh_host_rsa_key + sshd: + Match: + - Condition: "User xusers" + X11Forwarding: yes + Banner: /tmp/xusers-banner + sshd_match: + - Condition: "User bot" + AllowTcpForwarding: no + Banner: /tmp/bot-banner + sshd_match_1: + - Condition: "User sftponly" + ForceCommand: "internal-sftp" + ChrootDirectory: "/var/uploads/" + sshd_match_2: + - Condition: "User root" + PasswordAuthentication: no + PermitTunnel: yes + + - name: Verify the options are correctly set + vars: + main_sshd_config: >- + {{ + "/etc/ssh/sshd_config.d/00-ansible_system_role.conf" + if ansible_facts['distribution'] == 'Fedora' + else "/etc/ssh/sshd_config" + }} + block: + - meta: flush_handlers + + - name: List effective configuration using sshd -T for xusers + command: sshd -T -C user=xusers,addr=127.0.0.1,host=example.com + register: xusers_effective + + - name: List effective configuration using sshd -T for bot + command: sshd -T -C user=bot,addr=127.0.0.1,host=example.com + register: bot_effective + + - name: List effective configuration using sshd -T for sftponly + command: sshd -T -C user=sftponly,addr=127.0.0.1,host=example.com + register: sftponly_effective + + - name: List effective configuration using sshd -T for root + command: sshd -T -C user=root,addr=127.0.0.1,host=example.com + register: root_effective + + - name: Print current configuration file + slurp: + src: "{{ main_sshd_config }}" + register: config + + - name: Check the options are effective + # note, the options are in lower-case here + assert: + that: + - "'x11forwarding yes' in xusers_effective.stdout" + - "'banner /tmp/xusers-banner' in xusers_effective.stdout" + - "'allowtcpforwarding no' in bot_effective.stdout" + - "'banner /tmp/bot-banner' in bot_effective.stdout" + - "'forcecommand internal-sftp' in sftponly_effective.stdout" + - "'chrootdirectory /var/uploads/' in sftponly_effective.stdout" + - "'passwordauthentication no' in root_effective.stdout" + - "'permittunnel yes' in root_effective.stdout" + + - name: Check the options are in configuration file + assert: + that: + - "'Match User xusers' in config.content | b64decode" + - "'Match User bot' in config.content | b64decode" + - "'Match User sftponly' in config.content | b64decode" + - "'Match User root' in config.content | b64decode" + tags: tests::verify diff --git a/roles/sshd/tests/tests_match_iterate.yml b/roles/sshd/tests/tests_match_iterate.yml new file mode 100644 index 0000000..7c23564 --- /dev/null +++ b/roles/sshd/tests/tests_match_iterate.yml @@ -0,0 +1,79 @@ +--- +- hosts: all + tasks: + - name: Configure sshd + include_role: + name: ansible-sshd + vars: + # For Fedora containers, we need to make sure we have keys for sshd -T below + sshd_verify_hostkeys: + - /etc/ssh/ssh_host_rsa_key + sshd: + Match: + - Condition: "User xusers" + X11Forwarding: yes + Banner: /tmp/xusers-banner + - Condition: "User bot" + AllowTcpForwarding: no + Banner: /tmp/bot-banner + sshd_match: + - Condition: "User sftponly" + ForceCommand: "internal-sftp" + ChrootDirectory: "/var/uploads/" + - Condition: "User root" + PasswordAuthentication: no + PermitTunnel: yes + + - name: Verify the options are correctly set + vars: + main_sshd_config: >- + {{ + "/etc/ssh/sshd_config.d/00-ansible_system_role.conf" + if ansible_facts['distribution'] == 'Fedora' + else "/etc/ssh/sshd_config" + }} + block: + - meta: flush_handlers + + - name: List effective configuration using sshd -T for xusers + command: sshd -T -C user=xusers,addr=127.0.0.1,host=example.com + register: xusers_effective + + - name: List effective configuration using sshd -T for bot + command: sshd -T -C user=bot,addr=127.0.0.1,host=example.com + register: bot_effective + + - name: List effective configuration using sshd -T for sftponly + command: sshd -T -C user=sftponly,addr=127.0.0.1,host=example.com + register: sftponly_effective + + - name: List effective configuration using sshd -T for root + command: sshd -T -C user=root,addr=127.0.0.1,host=example.com + register: root_effective + + - name: Print current configuration file + slurp: + src: "{{ main_sshd_config }}" + register: config + + - name: Check the options are effective + # note, the options are in lower-case here + assert: + that: + - "'x11forwarding yes' in xusers_effective.stdout" + - "'banner /tmp/xusers-banner' in xusers_effective.stdout" + - "'allowtcpforwarding no' in bot_effective.stdout" + - "'banner /tmp/bot-banner' in bot_effective.stdout" + - "'forcecommand internal-sftp' in sftponly_effective.stdout" + - "'chrootdirectory /var/uploads/' in sftponly_effective.stdout" + - "'passwordauthentication no' in root_effective.stdout" + - "'permittunnel yes' in root_effective.stdout" + + - name: Check the options are in configuration file + assert: + that: + - "'Match User xusers' in config.content | b64decode" + - "'Match User bot' in config.content | b64decode" + - "'Match User sftponly' in config.content | b64decode" + - "'Match User root' in config.content | b64decode" + tags: tests::verify diff --git a/roles/sshd/tests/tests_set_common.yml b/roles/sshd/tests/tests_set_common.yml new file mode 100644 index 0000000..845bf76 --- /dev/null +++ b/roles/sshd/tests/tests_set_common.yml @@ -0,0 +1,44 @@ +--- +- hosts: all + tasks: + - name: Configure sshd + include_role: + name: ansible-sshd + vars: + sshd: + AcceptEnv: LANG + Banner: /etc/issue + Ciphers: aes256-gcm@openssh.com + Subsystem: "sftp internal-sftp" + sshd_config_file: /etc/ssh/sshd_config + + - name: Verify the options are correctly set + block: + - meta: flush_handlers + + - name: List effective configuration using sshd -T + command: sshd -T + register: runtime + + - name: Print current configuration file + slurp: + src: /etc/ssh/sshd_config + register: config + + - name: Check the options are effective + # note, the options are in lower-case here + assert: + that: + - "'acceptenv LANG' in runtime.stdout" + - "'banner /etc/issue' in runtime.stdout" + - "'ciphers aes256-gcm@openssh.com' in runtime.stdout" + - "'subsystem sftp internal-sftp' in runtime.stdout" + + - name: Check the options are in configuration file + assert: + that: + - "'AcceptEnv LANG' in config.content | b64decode" + - "'Banner /etc/issue' in config.content | b64decode" + - "'Ciphers aes256-gcm@openssh.com' in config.content | b64decode" + - "'Subsystem sftp internal-sftp' in config.content | b64decode" + tags: tests::verify diff --git a/roles/sshd/tests/tests_set_uncommon.yml b/roles/sshd/tests/tests_set_uncommon.yml new file mode 100644 index 0000000..13586f5 --- /dev/null +++ b/roles/sshd/tests/tests_set_uncommon.yml @@ -0,0 +1,50 @@ +--- +- hosts: all + tasks: + - name: Configure sshd with uncommon options, making sure it keeps running + block: + - name: Configure ssh with unsupported options + include_role: + name: ansible-sshd + vars: + sshd: + # Unsupported in new versions, but ignored ? + Protocol: 1 + UsePrivilegeSeparation: no + UseLogin: yes + # Debian only + DebianBanner: /etc/motd + # Used in FreeBSD ? + VersionAddendum: FreeBSD-20180909 + # HPN only + HPNDisabled: yes + HPNBufferSize: 2MB + TcpRcvBufPoll: yes + NoneEnabled: yes + # some builds might be without kerberos/GSSAPI + KerberosAuthentication: yes + GSSAPIStoreCredentialsOnRekey: yes + # SSHv1 options + KeyRegenerationInterval: 1h + ServerKeyBits: 1024 + # This one is pretty new, but works on OpenBSD only + RDomain: 2 + register: role_result + + - name: unreachable task + fail: + msg: UNREACH + rescue: + - name: Check that we failed in the role + assert: + that: + - ansible_failed_result.msg != 'UNREACH' + - not role_result.changed + msg: "Role has not failed when it should have" + + - name: Make sure service is still running + service: + name: sshd + state: started + register: result + failed_when: result.changed diff --git a/roles/sshd/tests/tests_sysconfig.yml b/roles/sshd/tests/tests_sysconfig.yml new file mode 100644 index 0000000..872958d --- /dev/null +++ b/roles/sshd/tests/tests_sysconfig.yml @@ -0,0 +1,41 @@ +--- +- hosts: all + tasks: + - name: Configure sshd + include_role: + name: ansible-sshd + vars: + sshd_sysconfig: true + sshd_sysconfig_override_crypto_policy: true + sshd_sysconfig_use_strong_rng: 32 + + - name: Verify the options are correctly set + block: + - meta: flush_handlers + + - name: Print current configuration file + slurp: + src: /etc/sysconfig/sshd + register: config + + - name: Check the crypto policies is overridden in RHEL 8 + assert: + that: + - "'CRYPTO_POLICY=' in config.content | b64decode" + # these are string variants in default configuration file + - "'# CRYPTO_POLICY=' not in config.content | b64decode" + when: + - ansible_facts['os_family'] == "RedHat" + - ansible_facts['distribution_major_version'] == "8" + + - name: Check the RNG options are in configuration file + assert: + that: + - "'SSH_USE_STRONG_RNG=32' in config.content | b64decode" + # these are string variants in default configuration file + - "'SSH_USE_STRONG_RNG=0' not in config.content | b64decode" + - "'# SSH_USE_STRONG_RNG=1' not in config.content | b64decode" + when: + - ansible_facts['os_family'] == "RedHat" + - ansible_facts['distribution'] != 'Fedora' + tags: tests::verify diff --git a/roles/sshd/vars/AIX.yml b/roles/sshd/vars/AIX.yml new file mode 100644 index 0000000..0e9fb64 --- /dev/null +++ b/roles/sshd/vars/AIX.yml @@ -0,0 +1,14 @@ +--- +sshd_config_mode: '0644' +# sshd is not installed by yum / AIX toolbox for Linux. +# You'll need to manually install them using AIX Web Download Packs. +sshd_packages: [] +sshd_sftp_server: /usr/sbin/sftp-server +sshd_config_group: system +__sshd_defaults: + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes + +sshd_install_service: no +sshd_manage_service: no +sshd_allow_reload: yes diff --git a/roles/sshd/vars/Amazon.yml b/roles/sshd/vars/Amazon.yml new file mode 100644 index 0000000..c010292 --- /dev/null +++ b/roles/sshd/vars/Amazon.yml @@ -0,0 +1,23 @@ +--- +sshd_config_mode: '0644' +sshd_packages: + - openssh + - openssh-server +sshd_sftp_server: /usr/libexec/openssh/sftp-server +__sshd_defaults: + SyslogFacility: AUTHPRIV + PermitRootLogin: forced-commands-only + AuthorizedKeysFile: .ssh/authorized_keys + PasswordAuthentication: no + ChallengeResponseAuthentication: no + UsePAM: yes + X11Forwarding: yes + PrintLastLog: yes + UsePrivilegeSeparation: sandbox + AcceptEnv: + - LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES + - LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT + - LC_IDENTIFICATION LC_ALL LANGUAGE + - XMODIFIERS + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Arch Linux.yml b/roles/sshd/vars/Arch Linux.yml new file mode 120000 index 0000000..d255bcd --- /dev/null +++ b/roles/sshd/vars/Arch Linux.yml @@ -0,0 +1 @@ +Archlinux.yml \ No newline at end of file diff --git a/roles/sshd/vars/Archlinux.yml b/roles/sshd/vars/Archlinux.yml new file mode 100644 index 0000000..de1da39 --- /dev/null +++ b/roles/sshd/vars/Archlinux.yml @@ -0,0 +1,11 @@ +--- +sshd_packages: + - openssh +sshd_sftp_server: /usr/lib/ssh/sftp-server +__sshd_defaults: + AuthorizedKeysFile: .ssh/authorized_keys + ChallengeResponseAuthentication: no + PrintMotd: no + Subsystem: "sftp {{ sshd_sftp_server }}" + UsePAM: yes +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Container Linux by CoreOS.yml b/roles/sshd/vars/Container Linux by CoreOS.yml new file mode 100644 index 0000000..656b455 --- /dev/null +++ b/roles/sshd/vars/Container Linux by CoreOS.yml @@ -0,0 +1,13 @@ +--- +# There is no package manager in CoreOS +sshd_packages: [] +sshd_service: sshd +sshd_sftp_server: internal-sftp +__sshd_defaults: + Subsystem: "sftp {{ sshd_sftp_server }}" + ClientAliveInterval: 180 + UseDNS: no + UsePAM: yes + PrintLastLog: no + PrintMotd: no +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Debian.yml b/roles/sshd/vars/Debian.yml new file mode 100644 index 0000000..a95c39b --- /dev/null +++ b/roles/sshd/vars/Debian.yml @@ -0,0 +1,36 @@ +--- +sshd_service: ssh +sshd_packages: + - openssh-server +sshd_config_mode: "0644" +__sshd_defaults: + Port: 22 + Protocol: 2 + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_dsa_key + - /etc/ssh/ssh_host_ecdsa_key + UsePrivilegeSeparation: yes + KeyRegenerationInterval: 3600 + ServerKeyBits: 768 + SyslogFacility: AUTH + LogLevel: INFO + LoginGraceTime: 120 + PermitRootLogin: yes + StrictModes: yes + RSAAuthentication: yes + PubkeyAuthentication: yes + IgnoreRhosts: yes + RhostsRSAAuthentication: no + HostbasedAuthentication: no + PermitEmptyPasswords: no + ChallengeResponseAuthentication: no + X11Forwarding: yes + X11DisplayOffset: 10 + PrintMotd: no + PrintLastLog: yes + TCPKeepAlive: yes + AcceptEnv: LANG LC_* + Subsystem: "sftp {{ sshd_sftp_server }}" + UsePAM: yes +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Debian_10.yml b/roles/sshd/vars/Debian_10.yml new file mode 100644 index 0000000..cca5691 --- /dev/null +++ b/roles/sshd/vars/Debian_10.yml @@ -0,0 +1,34 @@ +--- +sshd_service: ssh +sshd_packages: + - openssh-server + - openssh-sftp-server +sshd_config_mode: "0644" +__sshd_defaults: + Port: 22 + Protocol: 2 + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_ed25519_key + HostKeyAlgorithms: ssh-ed25519,ecdsa-sha2-nistp256,ssh-rsa,ssh-ed25519-cert-v01@openssh.com + KexAlgorithms: curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group-exchange-sha256 + MACs: umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com + SyslogFacility: AUTH + LogLevel: INFO + LoginGraceTime: 120 + PermitRootLogin: without-password + StrictModes: yes + PubkeyAuthentication: yes + IgnoreRhosts: yes + HostbasedAuthentication: no + PermitEmptyPasswords: no + ChallengeResponseAuthentication: no + X11Forwarding: yes + X11DisplayOffset: 10 + PrintMotd: no + PrintLastLog: yes + TCPKeepAlive: yes + AcceptEnv: LANG LC_* + Subsystem: "sftp {{ sshd_sftp_server }}" + UsePAM: yes +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Debian_8.yml b/roles/sshd/vars/Debian_8.yml new file mode 100644 index 0000000..f559c00 --- /dev/null +++ b/roles/sshd/vars/Debian_8.yml @@ -0,0 +1,38 @@ +--- +sshd_service: ssh +sshd_packages: + - openssh-server + - openssh-sftp-server +sshd_config_mode: "0644" +__sshd_defaults: + Port: 22 + Protocol: 2 + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_dsa_key + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ed25519_key + UsePrivilegeSeparation: yes + KeyRegenerationInterval: 3600 + ServerKeyBits: 1024 + SyslogFacility: AUTH + LogLevel: INFO + LoginGraceTime: 120 + PermitRootLogin: without-password + StrictModes: yes + RSAAuthentication: yes + PubkeyAuthentication: yes + IgnoreRhosts: yes + RhostsRSAAuthentication: no + HostbasedAuthentication: no + PermitEmptyPasswords: no + ChallengeResponseAuthentication: no + X11Forwarding: yes + X11DisplayOffset: 10 + PrintMotd: no + PrintLastLog: yes + TCPKeepAlive: yes + AcceptEnv: LANG LC_* + Subsystem: "sftp {{ sshd_sftp_server }}" + UsePAM: yes +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Debian_9.yml b/roles/sshd/vars/Debian_9.yml new file mode 100644 index 0000000..10745d2 --- /dev/null +++ b/roles/sshd/vars/Debian_9.yml @@ -0,0 +1,34 @@ +--- +sshd_service: ssh +sshd_packages: + - openssh-server + - openssh-sftp-server +sshd_config_mode: "0644" +__sshd_defaults: + Port: 22 + Protocol: 2 + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_dsa_key + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ed25519_key + UsePrivilegeSeparation: yes + SyslogFacility: AUTH + LogLevel: INFO + LoginGraceTime: 120 + PermitRootLogin: without-password + StrictModes: yes + PubkeyAuthentication: yes + IgnoreRhosts: yes + HostbasedAuthentication: no + PermitEmptyPasswords: no + ChallengeResponseAuthentication: no + X11Forwarding: yes + X11DisplayOffset: 10 + PrintMotd: no + PrintLastLog: yes + TCPKeepAlive: yes + AcceptEnv: LANG LC_* + Subsystem: "sftp {{ sshd_sftp_server }}" + UsePAM: yes +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Fedora.yml b/roles/sshd/vars/Fedora.yml new file mode 100644 index 0000000..f6f5df2 --- /dev/null +++ b/roles/sshd/vars/Fedora.yml @@ -0,0 +1,13 @@ +--- +sshd_packages: + - openssh + - openssh-server +sshd_sftp_server: /usr/libexec/openssh/sftp-server +# Fedora 32 ships with drop-in directory support so we touch +# just included file with highest priority by default and have +# empty defaults +sshd_config_file: /etc/ssh/sshd_config.d/00-ansible_system_role.conf +__sshd_defaults: +__sshd_os_supported: yes +sshd_hostkey_group: ssh_keys +sshd_hostkey_mode: "0640" diff --git a/roles/sshd/vars/Fedora_31.yml b/roles/sshd/vars/Fedora_31.yml new file mode 100644 index 0000000..4aa1cfe --- /dev/null +++ b/roles/sshd/vars/Fedora_31.yml @@ -0,0 +1,28 @@ +--- +sshd_packages: + - openssh + - openssh-server +sshd_sftp_server: /usr/libexec/openssh/sftp-server +__sshd_defaults: + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ed25519_key + SyslogFacility: AUTHPRIV + AuthorizedKeysFile: .ssh/authorized_keys + PasswordAuthentication: yes + ChallengeResponseAuthentication: no + GSSAPIAuthentication: yes + GSSAPICleanupCredentials: no + UsePAM: yes + X11Forwarding: yes + AcceptEnv: + - LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES + - LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT + - LC_IDENTIFICATION LC_ALL LANGUAGE + - XMODIFIERS + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes +__sshd_sysconfig_supports_crypto_policy: true +sshd_hostkey_group: ssh_keys +sshd_hostkey_mode: "0640" diff --git a/roles/sshd/vars/FreeBSD.yml b/roles/sshd/vars/FreeBSD.yml new file mode 100644 index 0000000..44f9cd0 --- /dev/null +++ b/roles/sshd/vars/FreeBSD.yml @@ -0,0 +1,5 @@ +--- +sshd_config_group: wheel +sshd_config_mode: "0644" +sshd_sftp_server: /usr/libexec/sftp-server +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Gentoo.yml b/roles/sshd/vars/Gentoo.yml new file mode 100644 index 0000000..efb54df --- /dev/null +++ b/roles/sshd/vars/Gentoo.yml @@ -0,0 +1,32 @@ +--- +sshd_packages: + - net-misc/openssh +sshd_sftp_server: /usr/lib64/misc/sftp-server +__sshd_defaults: + Subsystem: "sftp {{ sshd_sftp_server }}" + # Replace tcp keepalive with unspoofable keepalive + TCPKeepAlive: no + ClientAliveInterval: 300 + ClientAliveCountMax: 2 + # Secure cipher and algorithm settings + HostKey: + - /etc/ssh/ssh_host_ed25519_key + - /etc/ssh/ssh_host_rsa_key + HostKeyAlgorithms: "ssh-ed25519,ssh-rsa,ssh-ed25519-cert-v01@openssh.com" + KexAlgorithms: "curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256" + Ciphers: "chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" + MACs: "hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com" + AuthorizedKeysFile: .ssh/authorized_keys + # Security settings + PasswordAuthentication: no + ChallengeResponseAuthentication: no + PermitRootLogin: no + # Login settings + UsePAM: yes + PrintMotd: no + PrintLastLog: yes + # Disable most forwarding types for more security + AllowAgentForwarding: no + AllowTcpForwarding: no + AllowStreamLocalForwarding: no +__sshd_os_supported: yes diff --git a/roles/sshd/vars/OpenBSD.yml b/roles/sshd/vars/OpenBSD.yml new file mode 100644 index 0000000..b0c0d26 --- /dev/null +++ b/roles/sshd/vars/OpenBSD.yml @@ -0,0 +1,9 @@ +--- +sshd_config_group: wheel +sshd_config_mode: "0600" +sshd_sftp_server: /usr/libexec/sftp-server +__sshd_defaults: + AuthorizedKeysFile: .ssh/authorized_keys + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes +__sshd_manage_var_run: no diff --git a/roles/sshd/vars/RedHat_6.yml b/roles/sshd/vars/RedHat_6.yml new file mode 100644 index 0000000..5b9ccb7 --- /dev/null +++ b/roles/sshd/vars/RedHat_6.yml @@ -0,0 +1,24 @@ +--- +sshd_packages: + - openssh + - openssh-server +sshd_sftp_server: /usr/libexec/openssh/sftp-server +__sshd_defaults: + Protocol: 2 + SyslogFacility: AUTHPRIV + PasswordAuthentication: yes + ChallengeResponseAuthentication: no + GSSAPIAuthentication: yes + GSSAPICleanupCredentials: yes + UsePAM: yes + AcceptEnv: + - LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES + - LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT + - LC_IDENTIFICATION LC_ALL LANGUAGE + - XMODIFIERS + X11Forwarding: yes + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes +__sshd_sysconfig_supports_use_strong_rng: true +sshd_hostkey_group: ssh_keys +sshd_hostkey_mode: "0640" diff --git a/roles/sshd/vars/RedHat_7.yml b/roles/sshd/vars/RedHat_7.yml new file mode 100644 index 0000000..2d14066 --- /dev/null +++ b/roles/sshd/vars/RedHat_7.yml @@ -0,0 +1,31 @@ +--- +sshd_packages: + - openssh + - openssh-server +sshd_sftp_server: /usr/libexec/openssh/sftp-server +__sshd_defaults: + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ed25519_key + SyslogFacility: AUTHPRIV + AuthorizedKeysFile: .ssh/authorized_keys + PasswordAuthentication: yes + ChallengeResponseAuthentication: no + GSSAPIAuthentication: yes + GSSAPICleanupCredentials: no + # Note that UsePAM: no is not supported under RHEL/CentOS. See + # https://github.com/willshersystems/ansible-sshd/pull/51#issuecomment-287333218 + UsePAM: yes + X11Forwarding: yes + UsePrivilegeSeparation: sandbox + AcceptEnv: + - LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES + - LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT + - LC_IDENTIFICATION LC_ALL LANGUAGE + - XMODIFIERS + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes +__sshd_sysconfig_supports_use_strong_rng: true +sshd_hostkey_group: ssh_keys +sshd_hostkey_mode: "0640" diff --git a/roles/sshd/vars/RedHat_8.yml b/roles/sshd/vars/RedHat_8.yml new file mode 100644 index 0000000..909338f --- /dev/null +++ b/roles/sshd/vars/RedHat_8.yml @@ -0,0 +1,32 @@ +--- +sshd_packages: + - openssh + - openssh-server +sshd_sftp_server: /usr/libexec/openssh/sftp-server +__sshd_defaults: + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ed25519_key + SyslogFacility: AUTHPRIV + AuthorizedKeysFile: .ssh/authorized_keys + PasswordAuthentication: yes + ChallengeResponseAuthentication: no + GSSAPIAuthentication: yes + GSSAPICleanupCredentials: no + # Note that UsePAM: no is not supported under RHEL/CentOS. See + # https://github.com/willshersystems/ansible-sshd/pull/51#issuecomment-287333218 + UsePAM: yes + X11Forwarding: yes + PrintMotd: no + AcceptEnv: + - LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES + - LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT + - LC_IDENTIFICATION LC_ALL LANGUAGE + - XMODIFIERS + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes +__sshd_sysconfig_supports_use_strong_rng: true +__sshd_sysconfig_supports_crypto_policy: true +sshd_hostkey_group: ssh_keys +sshd_hostkey_mode: "0640" diff --git a/roles/sshd/vars/Suse.yml b/roles/sshd/vars/Suse.yml new file mode 100644 index 0000000..167010f --- /dev/null +++ b/roles/sshd/vars/Suse.yml @@ -0,0 +1,25 @@ +--- +sshd_packages: + - openssh +sshd_sftp_server: /usr/lib/ssh/sftp-server +__sshd_defaults: + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ed25519_key + SyslogFacility: AUTH + AuthorizedKeysFile: .ssh/authorized_keys + PasswordAuthentication: yes + ChallengeResponseAuthentication: no + GSSAPIAuthentication: yes + GSSAPICleanupCredentials: no + UsePAM: yes + X11Forwarding: yes + UsePrivilegeSeparation: sandbox + AcceptEnv: + - LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES + - LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT + - LC_IDENTIFICATION LC_ALL LANGUAGE + - XMODIFIERS + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Ubuntu_12.yml b/roles/sshd/vars/Ubuntu_12.yml new file mode 100644 index 0000000..a95c39b --- /dev/null +++ b/roles/sshd/vars/Ubuntu_12.yml @@ -0,0 +1,36 @@ +--- +sshd_service: ssh +sshd_packages: + - openssh-server +sshd_config_mode: "0644" +__sshd_defaults: + Port: 22 + Protocol: 2 + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_dsa_key + - /etc/ssh/ssh_host_ecdsa_key + UsePrivilegeSeparation: yes + KeyRegenerationInterval: 3600 + ServerKeyBits: 768 + SyslogFacility: AUTH + LogLevel: INFO + LoginGraceTime: 120 + PermitRootLogin: yes + StrictModes: yes + RSAAuthentication: yes + PubkeyAuthentication: yes + IgnoreRhosts: yes + RhostsRSAAuthentication: no + HostbasedAuthentication: no + PermitEmptyPasswords: no + ChallengeResponseAuthentication: no + X11Forwarding: yes + X11DisplayOffset: 10 + PrintMotd: no + PrintLastLog: yes + TCPKeepAlive: yes + AcceptEnv: LANG LC_* + Subsystem: "sftp {{ sshd_sftp_server }}" + UsePAM: yes +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Ubuntu_14.yml b/roles/sshd/vars/Ubuntu_14.yml new file mode 100644 index 0000000..f559c00 --- /dev/null +++ b/roles/sshd/vars/Ubuntu_14.yml @@ -0,0 +1,38 @@ +--- +sshd_service: ssh +sshd_packages: + - openssh-server + - openssh-sftp-server +sshd_config_mode: "0644" +__sshd_defaults: + Port: 22 + Protocol: 2 + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_dsa_key + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ed25519_key + UsePrivilegeSeparation: yes + KeyRegenerationInterval: 3600 + ServerKeyBits: 1024 + SyslogFacility: AUTH + LogLevel: INFO + LoginGraceTime: 120 + PermitRootLogin: without-password + StrictModes: yes + RSAAuthentication: yes + PubkeyAuthentication: yes + IgnoreRhosts: yes + RhostsRSAAuthentication: no + HostbasedAuthentication: no + PermitEmptyPasswords: no + ChallengeResponseAuthentication: no + X11Forwarding: yes + X11DisplayOffset: 10 + PrintMotd: no + PrintLastLog: yes + TCPKeepAlive: yes + AcceptEnv: LANG LC_* + Subsystem: "sftp {{ sshd_sftp_server }}" + UsePAM: yes +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Ubuntu_16.yml b/roles/sshd/vars/Ubuntu_16.yml new file mode 100644 index 0000000..2ee35c8 --- /dev/null +++ b/roles/sshd/vars/Ubuntu_16.yml @@ -0,0 +1,40 @@ +--- +sshd_service: ssh +sshd_packages: + - openssh-server + - openssh-sftp-server +sshd_config_mode: "0644" +__sshd_defaults: + Port: 22 + Protocol: 2 + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_dsa_key + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ed25519_key + UsePrivilegeSeparation: yes + KeyRegenerationInterval: 3600 + ServerKeyBits: 1024 + SyslogFacility: AUTH + LogLevel: INFO + LoginGraceTime: 120 + PermitRootLogin: prohibit-password + StrictModes: yes + RSAAuthentication: yes + PubkeyAuthentication: yes + AuthorizedKeysFile: "%h/.ssh/authorized_keys" + IgnoreRhosts: yes + RhostsRSAAuthentication: no + HostbasedAuthentication: no + PermitEmptyPasswords: no + ChallengeResponseAuthentication: no + X11Forwarding: yes + X11DisplayOffset: 10 + PrintMotd: no + PrintLastLog: yes + TCPKeepAlive: yes + AcceptEnv: LANG LC_* + Subsystem: "sftp {{ sshd_sftp_server }}" + UsePAM: yes + UseDNS: no +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Ubuntu_18.yml b/roles/sshd/vars/Ubuntu_18.yml new file mode 100644 index 0000000..da8a005 --- /dev/null +++ b/roles/sshd/vars/Ubuntu_18.yml @@ -0,0 +1,15 @@ +--- +sshd_service: ssh +sshd_packages: + - openssh-server + - openssh-sftp-server +sshd_config_mode: "0644" +__sshd_defaults: + PasswordAuthentication: no + ChallengeResponseAuthentication: no + UsePAM: yes + X11Forwarding: yes + PrintMotd: no + AcceptEnv: LANG LC_* + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes diff --git a/roles/sshd/vars/Ubuntu_20.yml b/roles/sshd/vars/Ubuntu_20.yml new file mode 100644 index 0000000..a60fba4 --- /dev/null +++ b/roles/sshd/vars/Ubuntu_20.yml @@ -0,0 +1,14 @@ +--- +sshd_service: ssh +sshd_packages: + - openssh-server + - openssh-sftp-server +sshd_config_mode: "0644" +__sshd_defaults: + ChallengeResponseAuthentication: no + UsePAM: yes + X11Forwarding: yes + PrintMotd: no + AcceptEnv: LANG LC_* + Subsystem: "sftp /usr/lib/openssh/sftp-server" +__sshd_os_supported: yes diff --git a/roles/sshd/vars/default.yml b/roles/sshd/vars/default.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/sshd/vars/default.yml @@ -0,0 +1 @@ +--- diff --git a/roles/sshd/vars/openSUSE Leap_15.yml b/roles/sshd/vars/openSUSE Leap_15.yml new file mode 100644 index 0000000..883627c --- /dev/null +++ b/roles/sshd/vars/openSUSE Leap_15.yml @@ -0,0 +1,14 @@ +--- +sshd_packages: + - openssh +sshd_sftp_server: /usr/lib/ssh/sftp-server +__sshd_defaults: + AuthorizedKeysFile: .ssh/authorized_keys + UsePAM: yes + X11Forwarding: yes + AcceptEnv: + - LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES + - LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT + - LC_IDENTIFICATION LC_ALL + Subsystem: "sftp {{ sshd_sftp_server }}" +__sshd_os_supported: yes diff --git a/site.yml b/site.yml index 231ace7..694c56d 100755 --- a/site.yml +++ b/site.yml @@ -10,6 +10,20 @@ tags: [ ansible, common ] - role: adminuser tags: [ adminuser, common ] + - role: sshd + vars: + sshd: + AcceptEnv: "LANG LC_*" + ChallengeResponseAuthentication: no + Compression: yes + PasswordAuthentication: no + PermitRootLogin: no + PrintMotd: no + PubkeyAuthentication: yes + Subsystem: "sftp /usr/lib/openssh/sftp-server" + UsePAM: yes + X11Forwarding: no + tags: [ sshd, common ] - role: git vars: git_repos: