├── .editorconfig ├── .luacheckrc ├── BoxFile ├── LICENSE ├── README.adoc ├── getkey-ldap.conf ├── install └── ssh-getkey-ldap /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most editorconfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [install] 13 | indent_style = tab 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = 'min' 2 | -------------------------------------------------------------------------------- /BoxFile: -------------------------------------------------------------------------------- 1 | lualdap 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2016 Jakub Jirutka . 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = OpenSSH, look up public keys in LDAP! 2 | :name: ssh-getkey-ldap 3 | :tag: v0.1.2 4 | :gh-name: jirutka/{name} 5 | :script-name: ssh-getkey-ldap 6 | :file-uri: https://raw.githubusercontent.com/{gh-name}/{tag} 7 | 8 | This project provides a simple script to be used as `AuthorizedKeysCommand` in OpenSSH server to fetch authorized keys from LDAP. 9 | The script is written in Lua and requires just one dependency, lualdap (and Lua interpreter of course). 10 | 11 | Not fan of Lua? 12 | Then you may try https://gist.github.com/jirutka/b15c31b2739a4f3eab63[this one] written in POSIX shell (but it requires `ldapsearch` utility and may not work well on some systems) or https://github.com/jirutka/ssh-ldap-pubkey[ssh-ldap-pubkey] written in Python. 13 | 14 | If you need an utility for users to manage keys stored in LDAP, https://github.com/jirutka/ssh-ldap-pubkey[ssh-ldap-pubkey] is what you’re looking for. 15 | 16 | 17 | == Requirements 18 | 19 | * Lua 5.1+ or LuaJIT 2.0+ 20 | * https://luarocks.org/modules/bdellegrazie/lualdap[lualdap] (that requires libldap) 21 | 22 | 23 | == Installation 24 | 25 | === On Alpine Linux 26 | 27 | [source, sh, subs="verbatim, attributes"] 28 | ---- 29 | apk add {name} 30 | ---- 31 | 32 | === Using git and ./install 33 | 34 | [source, sh, subs="verbatim, attributes"] 35 | ---- 36 | git clone -b {tag} https://github.com/{gh-name}.git 37 | cd {name} 38 | ./install 39 | ---- 40 | 41 | === Manual 42 | 43 | [source, sh, subs="verbatim, attributes"] 44 | ---- 45 | cd /usr/local/bin 46 | wget {file-uri}/{script-name} 47 | chown root:root {script-name} 48 | chmod 0755 {script-name} 49 | 50 | cd /etc/ssh 51 | wget {file-uri}/getkey-ldap.conf 52 | vim getkey-ldap.conf # read next section 53 | ---- 54 | 55 | 56 | == Configuration 57 | 58 | The script reads configuration from `/etc/ssh/getkey-ldap.conf`. 59 | 60 | The file format is similar to other UNIX configuration files. 61 | Comments begin with a `#` character and extend to the end of the line; blank lines are ignored. 62 | Configuration options consist of an initial keyword followed by a list of values separated by one or more whitespaces. 63 | Options may not be continued over multiple lines. 64 | Keywords and values are case-sensitive. 65 | 66 | The configuration options are as follows: 67 | 68 | host:: 69 | A list of hostnames or IP addresses of hosts running an LDAP server to connect to. 70 | Each hostname in the list may include a port number which is separated from the host itself with a colon `:` character. 71 | Default value is localhost. 72 | 73 | use_tls:: 74 | Whether to use TLS (true, or false). 75 | Default is false. 76 | 77 | binddn:: 78 | DN to bind when reading the user’s entry. 79 | Default is to bind anonymously. 80 | 81 | bindpw:: 82 | Credentials to bind with when reading the user’s entry. 83 | Default is none. 84 | 85 | base:: 86 | DN of the search base. 87 | Default is empty (i.e. root of the directory). 88 | 89 | scope:: 90 | The search scope; base, onelevel, or subtree. 91 | Default is subtree. 92 | 93 | timeout:: 94 | The timeout in seconds. 95 | Default is 5. 96 | 97 | pubkey_attr:: 98 | Name of the attribute with SSH pubkeys. 99 | Default is sshPublicKey. 100 | 101 | 102 | == Setup OpenSSH server 103 | 104 | To configure OpenSSH server to fetch users’ authorized keys from LDAP server: 105 | 106 | . Make sure that you have installed `{script-name}` in `/usr/local/bin` (or `/usr/bin`) with owner `root` and mode `0755`. 107 | . Add these two lines into `/etc/ssh/sshd_config`: 108 | + 109 | [source, subs="verbatim, attributes"] 110 | ---- 111 | AuthorizedKeysCommand /usr/local/bin/{script-name} 112 | AuthorizedKeysCommandUser nobody 113 | ---- 114 | 115 | . Restart sshd and check log file if there’s no problem. 116 | 117 | Note: This method is supported by OpenSSH since version 6.2-p1 (or 5.3 onRedHat). 118 | If you have an older version and can’t upgrade, for whatever weird reason, use http://code.google.com/p/openssh-lpk/[openssh-lpk] patch instead. 119 | 120 | 121 | == Setup LDAP server 122 | 123 | Just add the https://raw.githubusercontent.com/jirutka/ssh-ldap-pubkey/v0.4.1/etc/openssh-lpk.schema[openssh-lpk.schema] to your LDAP server, **or** add an attribute named `sshPublicKey` to any existing schema which is already defined in people entries. 124 | That’s all. 125 | 126 | Note: Presumably, you’ve already setup your LDAP server for centralized unix users management, i.e. you have the http://www.zytrax.com/books/ldap/ape/nis.html[NIS schema] and users in LDAP. 127 | 128 | 129 | == License 130 | 131 | This project is licensed under http://opensource.org/licenses/MIT[MIT License]. 132 | For the full text of the license, see the link:LICENSE[LICENSE] file. 133 | -------------------------------------------------------------------------------- /getkey-ldap.conf: -------------------------------------------------------------------------------- 1 | # /etc/ssh/getkey-ldap.conf 2 | # Configuration file for ssh-getkey-ldap. 3 | 4 | # A space-separated list of hostnames or IP addresses of hosts running an LDAP 5 | # server to connect to. Each hostname in the list may include a port number 6 | # separated by a colon (:) character. Default value is localhost. 7 | host localhost 8 | 9 | # Whether to use TLS (true, or false). Default is false. 10 | use_tls false 11 | 12 | # DN to bind when reading the user's entry. Default is to bind anonymously. 13 | #binddn cn=admin,ou=example,c=org 14 | 15 | # Credentials to bind with when reading the user's entry. Default is none. 16 | #bindpw top-secret 17 | 18 | # DN of the search base. Default is empty (i.e. root of the directory). 19 | base ou=People,ou=example,c=org 20 | 21 | # The search scope; base, onelevel, or subtree. Default is subtree. 22 | scope subtree 23 | 24 | # The timeout in seconds. Default is 5. 25 | timeout 5 26 | 27 | # Name of the attribute with SSH pubkeys. Default is sshPublicKey. 28 | pubkey_attr sshPublicKey 29 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | : ${DESTDIR:=/} 5 | : ${PREFIX:=/usr/local} 6 | 7 | bin_path="${DESTDIR}${PREFIX}/bin/ssh-getkey-ldap" 8 | conf_path="${DESTDIR}/etc/ssh/getkey-ldap.conf" 9 | 10 | mkdir -p "$(dirname "$bin_path")" 11 | cp -v ssh-getkey-ldap "$bin_path" 12 | chown root "$bin_path" 13 | chmod 0755 "$bin_path" 14 | 15 | if [ ! -f "$conf_path" ]; then 16 | mkdir -p "$(dirname "$conf_path")" 17 | cp -v getkey-ldap.conf "$conf_path" 18 | chmod 0644 "$conf_path" 19 | fi 20 | 21 | cat <<-EOF 22 | 23 | If you want OpenSSH server to look up user's public keys in LDAP, 24 | add the following lines to /etc/ssh/sshd_config and reload sshd: 25 | 26 | AuthorizedKeysCommand $bin_path 27 | AuthorizedKeysCommandUser nobody 28 | 29 | EOF 30 | -------------------------------------------------------------------------------- /ssh-getkey-ldap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | ----- 3 | -- This script looks for SSH public keys in LDAP for the username specified 4 | -- as the first argument of the script and prints them to stdout. 5 | -- The configuration is read from file /etc/ssh/getkey-ldap.conf. 6 | -- 7 | -- To use as the AuthorizedKeysCommand in OpenSSH 6.2+, this file must be owned 8 | -- by root and not writable by group or others! 9 | -- 10 | -- Author: Jakub Jirutka 11 | -- Version: 0.1.2 12 | -- URL: https://github.com/jirutka/ssh-getkey-ldap 13 | -- 14 | 15 | local ldap = require 'lualdap' 16 | 17 | local CONFIG_FILE = '/etc/ssh/getkey-ldap.conf' 18 | local DEFAULT_CONF = { 19 | host = 'localhost', 20 | use_tls = false, 21 | binddn = nil, 22 | bindpw = nil, 23 | base = nil, 24 | scope = 'subtree', 25 | timeout = 5, 26 | pubkey_attr = 'sshPublicKey' 27 | } 28 | 29 | 30 | --- Functions --- 31 | 32 | local function log (level, msg, ...) 33 | msg = msg:format(...) 34 | io.stderr:write(("%s: %s\n"):format(level:upper(), msg)) 35 | os.execute(("logger -t sshd -p auth.%s 'ldap: %s'"):format(level, msg)) 36 | end 37 | 38 | local function die (msg, ...) 39 | log('err', msg, ...) 40 | os.exit(1) 41 | end 42 | 43 | local function merge (...) 44 | local res = {} 45 | for _, tab in ipairs {...} do 46 | for k, v in pairs(tab) do res[k] = v end 47 | end 48 | 49 | return res 50 | end 51 | 52 | local function parse_bool (str) 53 | if str == nil then 54 | return nil 55 | end 56 | return str:lower() == 'true' or str:lower() == 'yes' 57 | end 58 | 59 | local function parse_config (str) 60 | local conf = {} 61 | for line in str:gmatch('[^\r\n]+') do 62 | local key, val = line:match('^([%w_]+)%s+([^#]+)') 63 | if key then 64 | conf[key] = val:match('^(.-)%s*$') -- remove trailing whitespaces 65 | end 66 | end 67 | 68 | return conf 69 | end 70 | 71 | local function read_config (filepath) 72 | local file, err = io.open(filepath, 'r') 73 | if err then return nil, err end 74 | 75 | local conf = parse_config(file:read('*a')) 76 | file:close() 77 | 78 | conf.use_tls = parse_bool(conf.use_tls) 79 | conf.timeout = tonumber(conf.timeout) 80 | 81 | return merge(DEFAULT_CONF, conf) 82 | end 83 | 84 | 85 | --- Main --- 86 | 87 | local uid = arg[1] or die 'no username provided' 88 | 89 | local cfg, err1 = read_config(CONFIG_FILE) 90 | if err1 then die(err1) end 91 | 92 | local conn, err2 = ldap.open_simple(cfg.host, cfg.binddn, cfg.bindpw, cfg.use_tls) 93 | if err2 then die(err2) end 94 | 95 | local _, attrs = conn:search({ 96 | base = cfg.base, 97 | scope = cfg.scope, 98 | attrs = cfg.pubkey_attr, 99 | filter = ("(&(uid=%s)(%s=*))"):format(uid, cfg.pubkey_attr), 100 | timeout = cfg.timeout, 101 | sizelimit = 1 102 | })() 103 | 104 | local pubkeys = attrs and attrs[cfg.pubkey_attr] or {} 105 | 106 | -- Fixes https://github.com/jirutka/ssh-getkey-ldap/issues/1. 107 | if type(pubkeys) ~= 'table' then 108 | pubkeys = { pubkeys } 109 | end 110 | 111 | log('info', "Loaded %d SSH public key(s) from LDAP for user: %s", #pubkeys, uid) 112 | print(table.concat(pubkeys, '\n')) 113 | 114 | conn:close() 115 | --------------------------------------------------------------------------------