aboutsummaryrefslogtreecommitdiff
path: root/tasks/main.yml
blob: 74e0a32adb48fe788077e032ddd3a36b6b92dd4e53711b455af502b3de7fe77f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
---

# ✅  Existing local file   vault secret
# ✅  Existing local file   no vault
# ✅  No local file         vault secret
# ✅  No local file         no vault

# Sets up google authenticator and integrates with login

# Ensure we have the user's group
- when: groupname is not defined
  block:
    - name: Get passwd DB entry for {{ username }}
      ansible.builtin.getent:
        database: passwd
        key: "{{ username }}"
      register: user_pw

    - name: Get group DB entry for {{ username }}
      ansible.builtin.getent:
        database: group
        key: "{{ user_pw.ansible_facts.getent_passwd[username][2] }}"
      register: user_gid

    - name: Set groupname fact to {{ user_gid.ansible_facts.getent_group.keys()|first }}
      set_fact:
        groupname: "{{ user_gid.ansible_facts.getent_group.keys()|first }}"
  #
  # block

# Install packages
- name: Install google authenticator packages
  ansible.builtin.apt:
    state: present
    update_cache: true
    install_recommends: no
    name: "{{ google_auth_packages }}"
  become: yes

#
# If google_auth_config is defined then use those values to build .google_authenticator etc.
#

- name: Check for existing /home/{{ username }}/.google_authenticator config
  ansible.builtin.stat:
    path: "/home/{{ username }}/.google_authenticator"
  register: google_auth_config_local

# Only generate a new config if no existing local one
- when: not google_auth_config_local.stat.exists  # No existing secret
  block:

    # Create new .google_authenticator from vault config if vault defined
    - name: Create .google_authenticator file
      ansible.builtin.copy:
        content: "{{ google_auth_config | selectattr('name', 'equalto', inventory_hostname) | map(attribute='secret') | first }}"
        dest: "/home/{{ username }}/.google_authenticator"
        mode: '0400'
        owner: "{{ username }}"
        group: "{{ username }}"
      when:
        - google_auth_config != "NEW"               # vault_google_auth_config exists
        - google_auth_config | selectattr('name', 'equalto', inventory_hostname) | list | length > 0  # vault entry for THIS host exists

    # If no vault config defined, or no entry defined for this hostname
    - when: google_auth_config == "NEW" or google_auth_config | selectattr('name', 'equalto', inventory_hostname) | list | length == 0
      block:
        # Generate secret
        - name: Generate secret for google authenticator
          ansible.builtin.command: "/usr/bin/google-authenticator --time-based --disallow-reuse --label=email_{{ inventory_hostname }} --qr-mode=UTF8 --rate-limit=3 --rate-time=30 --secret=/home/{{ username }}/.google_authenticator --window-size=3 --force --quiet"
          args:
            creates: "/home/{{ username }}/.google_authenticator"
          register: google_auth_create

        # Set .google_authenticator to mode 400
        - name: Set new secret file permissions
          ansible.builtin.file:
            path: "/home/{{ username }}/.google_authenticator"
            owner: "{{ username }}"
            group: "{{ groupname }}"
            mode: '0400'
      #
      # block no vault config or no entry defined
  #
  # block not local config exists


#
# Now we deal with a .google_authernticator, regardless of whether it already existed
# or was newly created, or was created from a vault config
#
- name: Pulling in /home/{{ username }}/.google_authenticator
  ansible.builtin.slurp:
    src: "/home/{{ username }}/.google_authenticator"
  register: google_auth_file

- name: Set google auth config fact
  ansible.builtin.set_fact:
    google_auth_config_mine: "{{ google_auth_file['content'] | b64decode }}"

- name: Parse TOTP variable
  ansible.builtin.set_fact:
    totp_lines: "{{ google_auth_config_mine.split('\n') | map('trim') | list }}"

- name: Filter valid lines
  ansible.builtin.set_fact:
    valid_lines: "{{ totp_lines | reject('search', '^\"') | list }}"

# Main secret must be 16 or 26 chars.  Must be at least 5x scratch codes
- name: Validate secret and scratch codes
  ansible.builtin.assert:
    that:
      - "valid_lines[0] is defined and valid_lines[0] | length in [16, 26] and valid_lines[0] is match('^[A-Z0-9]+$')"
      - "valid_lines[1:] | select('match', '^[0-9]{8}$') | list | length >= 5"

    fail_msg: "The TOTP variable does not meet the required structure."
    success_msg: "The TOTP variable is valid."

- debug:
    var: google_auth_config_mine

# Capture secret key - GOOGLE_SECRET=$(head -1 .google_authenticator
- name: Extract Google Authenticator secret key
  ansible.builtin.set_fact:
    google_secret_key: "{{ valid_lines[0] }}"

# Capture scratch codes - this creates a var like
# google_scratch_codes:
#   - 21528074
#   - 86134509
#   - 79251446
- name: Extract Google Authenticator scratch codes
  ansible.builtin.set_fact:
    google_scratch_codes: "{{ valid_lines | select('match', '^[0-9]{8}$') | list }}"

- debug:
    var: google_secret_key
- debug:
    var: google_scratch_codes

# Create QR code
- name: Create QR code for secret
  ansible.builtin.command: "/usr/bin/qrencode -m 3 -t UTF8 otpauth://totp/{{ inventory_hostname }}:{{ username }}%3Fsecret={{ google_secret_key }}%3FIssuer={{ inventory_hostname_short }}_mailsys"
  register: google_auth_qrcode

- debug:
    msg: "{{ google_auth_qrcode.stdout }}"


- become: yes
  block:
    # Set pam to use google authenticator for ssh
    # echo "auth required pam_google_authenticator.so" >> /etc/pam.d/sshd
    - name: Set pam to use google authenticator for ssh
      ansible.builtin.lineinfile:
        path: /etc/pam.d/sshd
        insertafter: EOF
        line: 'auth required pam_google_authenticator.so'
        state: present

    - name: Modify sshd_config to use google authenticator
      ansible.builtin.copy:
        dest: /etc/ssh/sshd_config.d/70-google_auth.conf
        content: |
          #
          # For google authenticator
          #
          ChallengeResponseAuthentication yes

    - name: Modify sshd_config to force use of google authenticator
      ansible.builtin.copy:
        dest: /etc/ssh/sshd_config.d/71-google_auth.conf
        content: |
          #
          # For google authenticator to force use of token always
          #
          PasswordAuthentication no
          # Only when global google_auth_force is true OR specific inventory_hostname has force_auth: true
      when: google_auth_force == true or google_auth_config | selectattr('name', 'equalto', inventory_hostname) | selectattr('force_auth', 'equalto', true) | list | length > 0
  #
  # block system file updates