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