├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── defaults └── main.yml ├── files └── bsd.schema ├── handlers └── main.yml ├── meta └── main.yml ├── tasks └── main.yml ├── templates ├── ldap_init.sh.j2 ├── ldapd.conf.j2 └── users.ldif.j2 ├── tests ├── inventory └── test.yml └── vars └── main.yml /.gitignore: -------------------------------------------------------------------------------- 1 | ansible.cfg 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Aaron Bieber 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @printf '[defaults]\nroles_path=../\n' >ansible.cfg 4 | @ansible-playbook tests/test.yml -i tests/inventory 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | openbsd-ldapd 2 | ============= 3 | 4 | This role sets up an ldapd server geared towards serving logins to OpenBSD hosts. 5 | 6 | Requirements 7 | ------------ 8 | 9 | - OpenBSD 10 | 11 | 12 | Role Variables 13 | -------------- 14 | 15 | | Variable | Default Value | Description | 16 | | -------- | ------------- | ----------- | 17 | | `ldap_domain` | "bolddaemon" | This is the name of your domain. If your FQDN was "example.com", this value would be "example". | 18 | | `ldap_tld` | "com" | The TLD of your domain. If your FQDN is "example.com", this would be "com". | 19 | | `ldap_admin` | "admin" | The `rootdn`. This will ultimately resolve to: `cn={{ ldap_admin }},dc={{ ldap_domain }},dc={{ ldap_tld }}`. | 20 | | `ldap_admin_pass` | "welcome" | Password for `rootdn`. This will be piped to `encrypt(1)` and stored in `/etc/ldapd.conf` as a hash.| 21 | | `ldap_uid_start` | 1000 | The starting point for UID. This value auto-increments for every user defined in `ldap_users`. | 22 | | `ldap_gid_start` | 1000 | The starting point for GID. This also auto-increments. | 23 | | `ldap_users` | array of objects that include: `uid`, `first`, `last`, `pw_hash` and optionally `pubkey` and `shell`. | The users defined here will be added to `/root/users.ldif`, which can then be used to bootstrap the ldap server. | 24 | 25 | Dependencies 26 | ------------ 27 | 28 | - OpenBSD 29 | 30 | Example Playbook 31 | ---------------- 32 | 33 | - hosts: openbsd_ldap_servers 34 | roles: 35 | - { role: openbsd-ldapd, tags: ["ldapd"] } 36 | 37 | License 38 | ------- 39 | 40 | ``` 41 | /* 42 | * Copyright (c) 2018 Aaron Bieber 43 | * 44 | * Permission to use, copy, modify, and distribute this software for any 45 | * purpose with or without fee is hereby granted, provided that the above 46 | * copyright notice and this permission notice appear in all copies. 47 | * 48 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 49 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 50 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 51 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 52 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 53 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 54 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 55 | */ 56 | ``` 57 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ldap_domain: bolddaemon 4 | ldap_tld: com 5 | ldap_fqdn: "{{ ldap_domain }}.{{ ldap_tld }}" 6 | ldap_admin: admin 7 | # Generate with "encrypt -b a password", where password is the plain text 8 | # version to use. You must prepend {CRYPT}. The password from below is 9 | # "secret". 10 | #ldap_admin_pass: "{CRYPT}$2b$08$uqLDO.G1vuJYyu3j.0CGz.J3EHAVvAQ3bsBEfG9/P44viHq4RCq8O" 11 | ldap_admin_pass: "welcome" 12 | ldap_uid_start: 1000 13 | ldap_gid_start: 1000 14 | ldap_indexes: [ objectClass, cn, ou, uid, uidNumber, gidNumber, sn, givenName, mail] 15 | 16 | ldap_groups: [ 17 | { gidnum: 666, name: _ldap, member_uids: [ puffy ] } 18 | ] 19 | 20 | ldap_users: [ 21 | { uid: puffy, first: "Puffy", last: "Fish", pw_hash: "$2b$10$leismHABjnpr/lGk4bQ6ce374PYKP/8LyNDh7ZYJAgAG0CNFVdcj2", pubkey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDUJHLRqynSi5YpWU0v6xogi0hiRFdXAckAw+KOek6TA" }, 22 | ] 23 | -------------------------------------------------------------------------------- /files/bsd.schema: -------------------------------------------------------------------------------- 1 | attributetype ( 1.3.6.1.4.1.30155.115.2 NAME 'shadowPassword' 2 | DESC 'POSIX hashed password' 3 | EQUALITY caseExactIA5Match 4 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) 5 | 6 | attributetype ( 1.3.6.1.4.1.30155.115.3 NAME 'sshPublicKey' 7 | DESC 'SSH public key' 8 | EQUALITY caseExactIA5Match 9 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) 10 | 11 | objectclass ( 1.3.6.1.4.1.30155.115.1 NAME 'bsdAccount' 12 | SUP top 13 | AUXILIARY 14 | DESC 'Abstraction of an account with OpenBSD attributes' 15 | MUST ( uid ) 16 | MAY ( shadowPassword $ shadowExpire $ modifyTimestamp $ userClass $ 17 | sshPublicKey )) 18 | 19 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: restart ldapd 4 | service: name=ldapd state=restarted 5 | 6 | - name: restart ypldap 7 | service: name=ypldap state=restarted 8 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Aaron Bieber 3 | description: OpenBSD ldapd built for authentication 4 | 5 | license: BSD 6 | 7 | min_ansible_version: 2.0 8 | 9 | platforms: 10 | - name: OpenBSD 11 | versions: 12 | - 6.1 13 | 14 | galaxy_tags: ["OpenBSD", "ldapd"] 15 | 16 | dependencies: [] 17 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Install deps 4 | package: 5 | name: "{{ item }}" 6 | with_items: 7 | - openldap-client-- 8 | - py-openssl 9 | 10 | - name: Check for certs 11 | stat: 12 | path: "/etc/ldap/certs/{{ ansible_default_ipv4.interface }}.crt" 13 | register: crt 14 | 15 | - name: Create private key 16 | openssl_privatekey: 17 | path: "/etc/ldap/certs/{{ ansible_default_ipv4.interface }}.key" 18 | when: crt.stat.exists == False 19 | 20 | - name: Create csr 21 | openssl_csr: 22 | path: "/etc/ldap/certs/{{ ansible_default_ipv4.interface }}.csr" 23 | privatekey_path: "/etc/ldap/certs/{{ ansible_default_ipv4.interface }}.key" 24 | common_name: "{{ ansible_fqdn }}" 25 | subject_alt_name: "DNS:{{ ansible_nodename }},DNS:{{ ansible_fqdn }},DNS:{{ ldap_domain }}.{{ ldap_tld }},DNS:{{ inventory_hostname }}" 26 | country_name: "US" 27 | state_or_province_name: "CO" 28 | force: False 29 | when: crt.stat.exists == False 30 | 31 | - name: Create certificate 32 | openssl_certificate: 33 | path: "/etc/ldap/certs/{{ ansible_default_ipv4.interface }}.crt" 34 | privatekey_path: "/etc/ldap/certs/{{ ansible_default_ipv4.interface }}.key" 35 | csr_path: "/etc/ldap/certs/{{ ansible_default_ipv4.interface }}.csr" 36 | provider: selfsigned 37 | when: crt.stat.exists == False 38 | 39 | - name: Create example LDIF 40 | template: 41 | src: users.ldif.j2 42 | dest: /root/users.ldif 43 | owner: root 44 | mode: 0600 45 | 46 | - name: Copy bsd.schema 47 | copy: 48 | src: bsd.schema 49 | dest: /etc/ldap/bsd.schema 50 | mode: 0644 51 | owner: root 52 | group: wheel 53 | 54 | - name: Setup ldapd 55 | template: 56 | src: ldapd.conf.j2 57 | dest: /etc/ldapd.conf 58 | mode: 0600 59 | owner: root 60 | group: wheel 61 | validate: /usr/sbin/ldapd -n -f %s 62 | notify: restart ldapd 63 | 64 | - name: Start and enable services 65 | service: 66 | name: "{{ item }}" 67 | state: started 68 | enabled: true 69 | with_items: 70 | - ldapd 71 | 72 | - name: Create ldap_init.sh 73 | template: 74 | src: ldap_init.sh.j2 75 | dest: /root/ldap_init.sh 76 | owner: root 77 | mode: 0700 78 | -------------------------------------------------------------------------------- /templates/ldap_init.sh.j2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ldapadd -x -D 'cn={{ ldap_admin }},dc={{ ldap_domain }},dc={{ ldap_tld }}' -W -f /root/users.ldif 4 | -------------------------------------------------------------------------------- /templates/ldapd.conf.j2: -------------------------------------------------------------------------------- 1 | # $OpenBSD: ldapd.conf,v 1.1 2014/07/11 21:20:10 deraadt Exp $ 2 | # 3 | # {{ ansible_managed }} 4 | # 5 | 6 | schema "/etc/ldap/core.schema" 7 | schema "/etc/ldap/inetorgperson.schema" 8 | schema "/etc/ldap/nis.schema" 9 | schema "/etc/ldap/bsd.schema" 10 | 11 | listen on lo0 secure 12 | listen on {{ ansible_default_ipv4.interface }} ldaps 13 | listen on "/var/run/ldapi" 14 | 15 | namespace "dc={{ ldap_domain }},dc={{ ldap_tld }}" { 16 | rootdn "cn={{ ldap_admin }},dc={{ ldap_domain }},dc={{ ldap_tld }}" 17 | rootpw "{CRYPT}{{ lookup('pipe', 'encrypt -b a ' + ldap_admin_pass|quote ) }}" 18 | {% for i in ldap_indexes %} 19 | index {{ i }} 20 | {% endfor %} 21 | #deny access to any by any 22 | #allow read access to children of "ou=People,dc={{ ldap_domain }},dc={{ ldap_tld }}" by "cn={{ ldap_read }},dc={{ ldap_domain }},dc={{ ldap_tld }}" 23 | #allow write access to any by self 24 | } 25 | -------------------------------------------------------------------------------- /templates/users.ldif.j2: -------------------------------------------------------------------------------- 1 | dn: dc={{ ldap_domain }},dc={{ ldap_tld }} 2 | dc: {{ ldap_domain }} 3 | objectClass: top 4 | objectClass: domain 5 | 6 | dn: cn={{ ldap_read }},dc={{ ldap_domain }},dc={{ ldap_tld }} 7 | cn: {{ ldap_read }} 8 | objectClass: top 9 | objectClass: simpleSecurityObject 10 | objectClass: organizationalRole 11 | userPassword: {{ ldap_read_pw }} 12 | 13 | dn: ou=People,dc={{ ldap_domain }},dc={{ ldap_tld }} 14 | ou: People 15 | objectClass: top 16 | objectClass: organizationalUnit 17 | 18 | dn: ou=Groups,dc={{ ldap_domain }},dc={{ ldap_tld }} 19 | ou: Groups 20 | objectClass: top 21 | objectClass: organizationalUnit 22 | 23 | {% for item in ldap_groups %} 24 | dn: cn={{ item.name }},ou=Groups,dc={{ ldap_domain }},dc={{ ldap_tld }} 25 | objectClass: posixGroup 26 | objectClass: top 27 | cn: {{ item.name }} 28 | userPassword: {crypt}* 29 | gidNumber: {{ item.gidnum }} 30 | {% for uid in item.member_uids %} 31 | memberUid: {{ uid }} 32 | {% endfor %} 33 | 34 | {% endfor %} 35 | {% for item in ldap_users %} 36 | dn: cn={{ item.uid }},ou=Groups,dc={{ ldap_domain }},dc={{ ldap_tld }} 37 | objectClass: posixGroup 38 | objectClass: top 39 | cn: {{ item.uid }} 40 | userPassword: {crypt}* 41 | gidNumber: {{ ldap_gid_start + loop.index }} 42 | 43 | dn: uid={{ item.uid }},ou=People,dc={{ ldap_domain }},dc={{ ldap_tld }} 44 | uid: {{ item.uid }} 45 | cn: {{ item.first }} {{ item.last }} 46 | sn: {{ item.first }} {{ item.last }} 47 | objectClass: top 48 | objectClass: posixAccount 49 | objectClass: bsdAccount 50 | objectClass: inetOrgPerson 51 | objectClass: shadowAccount 52 | userPassword: {BSDAUTH}{{ item.uid }} 53 | shadowPassword: {{ item.pw_hash }} 54 | uidNumber: {{ ldap_uid_start + loop.index }} 55 | gidNumber: {{ ldap_gid_start + loop.index }} 56 | userClass: default 57 | shadowExpire: 0 58 | gecos: {{ item.first }} {{ item.last }} 59 | homeDirectory: {{ item.home | default('/home/' + item.uid) }} 60 | {% if item.mail is defined %} 61 | mail: {{ item.mail }} 62 | {% endif %} 63 | {% if item.mobile is defined %} 64 | mobile: {{ item.mobile }} 65 | {% endif %} 66 | loginShell: {{ item.shell | default('/bin/ksh') }} 67 | {% if item.pubkey is defined %} 68 | sshPublicKey: {{ item.pubkey }} 69 | {% endif %} 70 | 71 | {% endfor %} 72 | 73 | -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | [testhosts] 2 | test 3 | 4 | [testhosts:vars] 5 | ansible_python_interpreter=/usr/local/bin/python2.7 6 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: testhosts 3 | remote_user: root 4 | roles: 5 | - openbsd-ldapd 6 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for ldapd --------------------------------------------------------------------------------