Skip to content

Instantly share code, notes, and snippets.

@advanceboy
Last active June 15, 2021 18:46
Show Gist options
  • Save advanceboy/a7f4c4be2d3941dea2c983b4483145c7 to your computer and use it in GitHub Desktop.
Save advanceboy/a7f4c4be2d3941dea2c983b4483145c7 to your computer and use it in GitHub Desktop.
Active Directory Joining Playbook for Ubuntu

Active Directory Joining Playbook for Ubuntu

  • For Ubuntu 20.04 LTS
    • Ansible-join-ads-ubuntu.yml
    • Example:
      • ansible-playbook -i inventory Ansible-join-ads-ubuntu.yml -e 'ad_admin_user=Administrator' -e 'ad_admin_user_password=password' -K
  • For CentOS 7:
    • Ansible-join-ads-centos.yml
    • Example:
      • ansible-playbook -i inventory Ansible-join-ads-centos.yml -e 'ad_admin_user=Administrator' -e 'ad_admin_user_password=password' -K
---
# Active Directory に参加して、 AD ユーザーでログイン可能な状態にする
# target: CentOS 7
# example:
# ansible-playbook -i inventory ansible-join-ads-centos.yml -e 'ad_admin_user=Administrator' -e 'ad_admin_user_password=password' -e '[email protected]' -K
# refs:
# - https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-file_and_print_servers#setting_up_samba_as_a_domain_member
# - https://docs.vmware.com/en/VMware-Horizon/2103/linux-desktops-setup/GUID-F8F0CFCF-C4D6-4784-85FF-E7C6DF575F49.html
- hosts: all
vars:
ntp_server: ntp.nict.jp
realm_name: MYLOCALAD.EXAMPLE.COM
domain_server: mylocalad.example.com
ad_admin_user: '{{ ad_admin_user | default(omit) }}' # Active Directory 管理者アカウント名
ad_admin_user_password: '{{ ad_admin_user_password | default(omit) }}' # Active Directory 管理者パスワード
no_reboot: no
permitted_users: null
tasks:
- name: fails if initial hostname
fail:
msg: 'failed as invalid hostname: ({{ansible_fqdn}})'
when: '"localhost" in ansible_fqdn'
# install pexpect pip package for ansible expect module
- name: update pre-setting packages
become: yes
yum:
name: epel-release
state: present
- name: install pip
become: yes
yum:
name: python-pip
state: present
- name: install pexpect
become: yes
pip:
name: pexpect
state: present
- name: install ad modules
become: yes
register: result_yum_samba
yum:
name:
- samba-winbind-clients
- samba-winbind-krb5-locator
- krb5-workstation
- chrony
state: present
- name: check whether samba has been updated or not
set_fact:
samba_daemon_state: '{{ "restarted" if result_yum_samba is changed else "started" }}'
# 時刻同期サーバを追加
- name: add AD to chrony sources
become: yes
register: result_chrony_conf
blockinfile:
path: /etc/chrony.conf
insertbefore: BOF
block: server {{ ntp_server }} iburst
- name: ensure chrony service
become: yes
service:
name: chronyd
state: '{{ "restarted" if result_chrony_conf is changed else "started" }}'
enabled: yes
# samba net で AD 参加されているかの確認
- name: check if machine is bound to AD
become: yes
register: result_netads_bound
# AD 未参加の状態では パスワードを聞かれるので、空行をパイプして入力をスキップする
shell: "echo '' | net ads testjoin"
changed_when: false
failed_when: false
- name:
set_fact:
needs_join_ad: '{{ result_netads_bound.rc != 0 or "Join is OK" not in result_netads_bound.stdout }}'
# AD 管理者 のユーザー名とパスワードの設定の確認
- name: ad_admin_user and ad_admin_user_password should be defined
assert:
that:
- ad_admin_user is defined and ad_admin_user != ''
- ad_admin_user_password is defined and ad_admin_user_password != ''
fail_msg: Set the value of 'ad_admin_user' and 'ad_admin_user_password' in the extra variables (or Job Template Survey in AWX).
when: needs_join_ad
# AD参加に必要な部分のみの smb.conf, nsswitch.conf, krb5.conf の更新
# ログインユーザーはシングルドメイン前提としているため id のマッピングは rid としている。
- name: update smb.conf for join to AD
become: yes
ini_file:
path: /etc/samba/smb.conf
create: no
section: global
option: '{{ item.opt }}'
value: '{{ item.val }}'
with_items:
- { opt: workgroup, val: '{{ realm_name.split(".")[0] }}' }
- { opt: security, val: ads }
- { opt: kerberos method, val: system keytab }
- { opt: template homedir, val: /home/%U }
- { opt: template shell, val: /bin/bash }
- { opt: realm, val: '{{ realm_name }}' }
- { opt: 'idmap config * : range', val: 10000-999999 }
- { opt: 'idmap config * : backend', val: tdb }
- { opt: 'idmap config {{ realm_name.split(".")[0] }} : range', val: 2000000-2999999 }
- { opt: 'idmap config {{ realm_name.split(".")[0] }} : backend', val: rid }
- { opt: winbind use default domain, val: 'yes' }
- { opt: winbind refresh tickets, val: 'yes' }
- { opt: winbind offline logon, val: 'yes' }
- { opt: winbind enum users, val: 'yes' }
- { opt: winbind enum groups, val: 'yes' }
- name: update nsswitch.conf
become: yes
lineinfile:
path: /etc/nsswitch.conf
backrefs: yes
regexp: '^({{ item }}:\s*(?!.*winbind).*)$'
line: '\1 winbind'
with_items:
- passwd
- group
- name: update krb5.conf
become: yes
copy:
dest: /etc/krb5.conf
owner: root
group: root
mode: u=rw,g=r,o=r
content: |
[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log
[libdefaults]
default_realm = {{ realm_name }}
forwardable = true
proxiable = true
dns_lookup_realm = false
renew_lifetime = 7d
rdns = false
pkinit_anchors = /etc/pki/tls/certs/ca-bundle.crt
default_ccache_name = KEYRING:persistent:%{uid}
[realms]
{{ realm_name }} = {
kdc = {{ domain_server }}
admin_server = {{ domain_server }}
}
[domain_realm]
.{{ domain_server }} = {{ realm_name }}
{{ domain_server }} = {{ realm_name }}
# net ads を使って AD に参加
- when: needs_join_ad
block:
- name: get kerberos ticket
become: yes
expect:
command: '/usr/bin/kinit {{ ad_admin_user }}'
responses:
'(?i)Password for ': '{{ ad_admin_user_password }}'
timeout: 300
no_log: yes
- name: join to Active Directory
become: yes
expect:
command: 'net ads join --no-dns-updates -U {{ ad_admin_user }}'
responses:
"(?i)Enter .*'s password:": '{{ ad_admin_user_password }}'
no_log: yes
# winbind は kinit 実行後に起動する必要がある。
- name: ensure winbind is {{ samba_daemon_state }}
become: yes
systemd:
name: "{{ item }}"
state: '{{ samba_daemon_state }}'
enabled: yes
with_items:
- winbind
# pam の設定で 'Winbind 認証' または 'ホームディレクトリーを作成' が無効になっていたら、有効にする
- name: check pam winbind-auth profile
shell: >-
cat /etc/pam.d/* | grep -E '^session\s+optional\s+pam_winbind.so'
changed_when: no
failed_when: no
register: result_grep_pam_winbind_auth
- name: check pam mkhomedir profile
shell: >-
cat /etc/pam.d/* | grep -E '^session\s+optional\s+pam_mkhomedir.so'
changed_when: no
failed_when: no
register: result_grep_pam_mkhomedir
- name: ensure pam winbind-auth and mkhomedir profile is enabled
become: yes
shell: authconfig --enablewinbind --enablewinbindauth --enablemkhomedir --update
when: result_grep_pam_winbind_auth.rc != 0 or result_grep_pam_mkhomedir.rc != 0
# permitted_users が存在する場合は、 AD で Linux にログインできるアカウントを、 ユーザーまたはグループ単位で制限
- name: update pam_winbind.conf for restricting login users
become: yes
ini_file:
path: /etc/security/pam_winbind.conf
create: yes
state: '{{ "present" if permitted_users is not none else "absent" }}'
section: global
option: require_membership_of
value: '{{ permitted_users | join(",") if (permitted_users is iterable and permitted_users is not string) else permitted_users }}'
register: result_pam_winbind_require_member
# GUI ログイン画面で, AD アカウントを使ったログインを可能にするため、システムを再起動する。
# no_reboot 追加変数を設定していた場合、再起動は行わない。
- when:
- not (no_reboot)
- result_yum_samba is changed or result_pam_winbind_require_member is changed
block:
- name: start rebooting
become: yes
shell: 'sleep 5 && reboot'
async: 1
poll: 0 # タスクの完了を待たずに次へ
- name: wait for rebooted
wait_for_connection:
delay: 30
timeout: 300
---
# Active Directory に参加して、 AD ユーザーでログイン可能な状態にする
# target: Ubuntu 20.04 LTS
# example:
# ansible-playbook -i inventory ansible-join-ads-ubuntu.yml -e 'ad_admin_user=Administrator' -e 'ad_admin_user_password=password' -e '[email protected]' -K
# refs:
# - https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-file_and_print_servers#setting_up_samba_as_a_domain_member
# - https://docs.vmware.com/en/VMware-Horizon/2103/linux-desktops-setup/GUID-F8F0CFCF-C4D6-4784-85FF-E7C6DF575F49.html
- hosts: all
vars:
ntp_server: ntp.nict.jp
realm_name: MYLOCALAD.EXAMPLE.COM
domain_server: mylocalad.example.com
ad_admin_user: '{{ ad_admin_user | default(omit) }}' # Active Directory 管理者アカウント名
ad_admin_user_password: '{{ ad_admin_user_password | default(omit) }}' # Active Directory 管理者パスワード
no_reboot: no
permitted_users: null
tasks:
- name: fails if initial hostname
fail:
msg: 'failed as invalid hostname: ({{ansible_fqdn}})'
when: '"localhost" in ansible_fqdn'
# install pexpect pip package for ansible expect module
- name: install pip
become: yes
apt:
update_cache: yes
name: python3-pip
state: present
- name: install pexpect
become: yes
pip:
name: pexpect
state: present
- name: install ad modules
become: yes
register: result_apt_samba
apt:
name:
- libnss-winbind
- libpam-winbind
- krb5-user
- chrony
state: present
- name: check whether samba has been updated or not
set_fact:
samba_daemon_state: '{{ "restarted" if result_apt_samba is changed else "started" }}'
# 時刻同期サーバを追加
- name: add AD to chrony sources
become: yes
register: result_chrony_conf
blockinfile:
path: /etc/chrony/chrony.conf
insertbefore: BOF
block: server {{ ntp_server }} iburst
- name: ensure chrony service
become: yes
service:
name: chrony.service
state: '{{ "restarted" if result_chrony_conf is changed else "started" }}'
enabled: yes
# samba net で AD 参加されているかの確認
- name: check if machine is bound to AD
become: yes
register: result_netads_bound
# AD 未参加の状態では パスワードを聞かれるので、空行をパイプして入力をスキップする
shell: "echo '' | net ads testjoin"
changed_when: false
failed_when: false
- name:
set_fact:
needs_join_ad: '{{ result_netads_bound.rc != 0 or "Join is OK" not in result_netads_bound.stdout }}'
# AD 管理者 のユーザー名とパスワードの設定の確認
- name: ad_admin_user and ad_admin_user_password should be defined
assert:
that:
- ad_admin_user is defined and ad_admin_user != ''
- ad_admin_user_password is defined and ad_admin_user_password != ''
fail_msg: Set the value of 'ad_admin_user' and 'ad_admin_user_password' in the extra variables (or Job Template Survey in AWX).
when: needs_join_ad
# AD参加に必要な部分のみの smb.conf, nsswitch.conf, krb5.conf の更新
# ログインユーザーはシングルドメイン前提としているため id のマッピングは rid としている。
- name: update smb.conf for join to AD
become: yes
ini_file:
path: /etc/samba/smb.conf
create: no
section: global
option: '{{ item.opt }}'
value: '{{ item.val }}'
with_items:
- { opt: workgroup, val: '{{ realm_name.split(".")[0] }}' }
- { opt: security, val: ads }
- { opt: kerberos method, val: system keytab }
- { opt: template homedir, val: /home/%U }
- { opt: template shell, val: /bin/bash }
- { opt: realm, val: '{{ realm_name }}' }
- { opt: 'idmap config * : range', val: 10000-999999 }
- { opt: 'idmap config * : backend', val: tdb }
- { opt: 'idmap config {{ realm_name.split(".")[0] }} : range', val: 2000000-2999999 }
- { opt: 'idmap config {{ realm_name.split(".")[0] }} : backend', val: rid }
- { opt: winbind use default domain, val: 'yes' }
- { opt: winbind refresh tickets, val: 'yes' }
- { opt: winbind offline logon, val: 'yes' }
- { opt: winbind enum users, val: 'yes' }
- { opt: winbind enum groups, val: 'yes' }
- name: update nsswitch.conf
become: yes
lineinfile:
path: /etc/nsswitch.conf
backrefs: yes
regexp: '^({{ item }}:\s*(?!.*winbind).*)$'
line: '\1 winbind'
with_items:
- passwd
- group
- name: update krb5.conf
become: yes
copy:
dest: /etc/krb5.conf
owner: root
group: root
mode: u=rw,g=r,o=r
content: |
[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log
[libdefaults]
default_realm = {{ realm_name }}
forwardable = true
proxiable = true
dns_lookup_realm = false
renew_lifetime = 7d
rdns = false
pkinit_anchors = /etc/pki/tls/certs/ca-bundle.crt
default_ccache_name = KEYRING:persistent:%{uid}
[realms]
{{ realm_name }} = {
kdc = {{ domain_server }}
admin_server = {{ domain_server }}
}
[domain_realm]
.{{ domain_server }} = {{ realm_name }}
{{ domain_server }} = {{ realm_name }}
# pam の設定で 'Create home directory on login' が無効になっていたら、有効にする
- name: check pam mkhomedir profile
shell: >-
cat /etc/pam.d/common-* | grep -E '^session\s+optional\s+pam_mkhomedir.so'
changed_when: no
failed_when: no
register: result_grep_pam_mkhomedir
- name: ensure pam mkhomedir profile is enabled
become: yes
shell: pam-auth-update --enable mkhomedir
when: result_grep_pam_mkhomedir.rc != 0
# net ads を使って AD に参加
- when: needs_join_ad
block:
- name: get kerberos ticket
become: yes
expect:
command: '/usr/bin/kinit {{ ad_admin_user }}'
responses:
'(?i)Password for ': '{{ ad_admin_user_password }}'
timeout: 300
no_log: yes
- name: join to Active Directory
become: yes
expect:
command: 'net ads join --no-dns-updates -U {{ ad_admin_user }}'
responses:
"(?i)Enter .*'s password:": '{{ ad_admin_user_password }}'
no_log: yes
# winbind は kinit 実行後に起動する必要がある。
- name: ensure winbind is {{ samba_daemon_state }}
become: yes
systemd:
name: "{{ item }}"
state: '{{ samba_daemon_state }}'
enabled: yes
with_items:
- winbind
# permitted_users が存在する場合は、 AD で Linux にログインできるアカウントを、 ユーザーまたはグループ単位で制限
- name: update pam_winbind.conf for restricting login users
become: yes
ini_file:
path: /etc/security/pam_winbind.conf
create: yes
state: '{{ "present" if permitted_users is not none else "absent" }}'
section: global
option: require_membership_of
value: '{{ permitted_users | join(",") if (permitted_users is iterable and permitted_users is not string) else permitted_users }}'
register: result_pam_winbind_require_member
# GUI ログイン画面で, AD アカウントを使ったログインを可能にするため、システムを再起動する。
# no_reboot 追加変数を設定していた場合、再起動は行わない。
- when:
- not (no_reboot)
- result_apt_samba is changed or result_pam_winbind_require_member is changed
block:
- name: start rebooting
become: yes
shell: 'sleep 5 && reboot'
async: 1
poll: 0 # タスクの完了を待たずに次へ
- name: wait for rebooted
wait_for_connection:
delay: 30
timeout: 300
MIT License
Copyright (c) 2021 advanceboy
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment