├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── backend.go ├── config.go ├── filesystem.go ├── handle_certificate_generate.go ├── handle_listen.go ├── handle_ssh.go ├── handle_table_generate.go ├── handle_validator.go ├── main.go ├── mongodb.go ├── table.go ├── tests ├── run_tests ├── setup.sh ├── teardown.sh └── testcases │ ├── change-password-if-tokens-are-same-as-retrieved.test.sh │ ├── do-not-change-password-if-tokens-do-not-match.test.sh │ ├── fs │ ├── add-ssh-key-with-truncate.test.sh │ ├── add-ssh-key.test.sh │ ├── generate-hash-table.test.sh │ ├── list-tokens.test.sh │ ├── next-requests-for-hash-must-return-another-hash.test.sh │ ├── retrieve-hash-for-token.test.sh │ └── retrieve-ssh-keys-for-token.test.sh │ ├── mongodb │ ├── add-ssh-key-with-truncate.test.sh │ ├── add-ssh-key.test.sh │ ├── generate-hash-table.test.sh │ ├── list-tokens.test.sh │ ├── next-requests-for-hash-must-return-another-hash.test.sh │ ├── retrieve-hash-for-token.test.sh │ └── retrieve-ssh-keys-for-token.test.sh │ └── retrieve-ten-unique-salts-for-specified-token.test.sh └── vendor └── .gitignore /.gitignore: -------------------------------------------------------------------------------- 1 | /shadowd 2 | /shadowd.test 3 | .last-testcase 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/github.com/reconquest/import.bash"] 2 | path = vendor/github.com/reconquest/import.bash 3 | url = https://github.com/reconquest/import.bash 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DATE=$(shell git log -1 --format="%cd" --date=short | sed s/-//g) 2 | BUILD_NUM=$(shell git rev-list --count HEAD) 3 | BUILD_HASH=$(shell git rev-parse --short HEAD) 4 | 5 | LDFLAGS="-X main.version=${BUILD_DATE}.${BUILD_NUM}_${BUILD_HASH}-1" 6 | GCFLAGS="-trimpath ${GOPATH}/src" 7 | 8 | build: 9 | go build -x -ldflags=${LDFLAGS} -gcflags ${GCFLAGS} . 10 | 11 | man: 12 | @ronn -r man.markdown 13 | 14 | clean: 15 | @git clean -ffdx 16 | 17 | test: 18 | tests/run_tests 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shadowd 2 | 3 | ![shadow horse](https://cloud.githubusercontent.com/assets/8445924/9289438/97f8a2e8-435f-11e5-853c-255a7fe22d08.png) 4 | 5 | **shadowd** is the secure login distribution service, which consists of two 6 | parts: server and client. 7 | 8 | In a typical server configuration case you should manually update the 9 | `/etc/shadow` and copy it on all servers (or via automatic configuration 10 | system); afterwards each server will have same hash in the `/etc/shadow`. 11 | Supposed that attacker successfully gained access to one of your servers and 12 | found collision to that single hash *attacker actually got access to all 13 | servers with that hash*. 14 | 15 | **shadowd** is summoned to solve that obscure problem. 16 | 17 | **shadowd solution** is to generate hash tables for specified passwords mixed 18 | with random salt for specified users and guarantee that a client receive unique 19 | hash. 20 | 21 | One **shadowd** instance can be used for securely instanciate thousands of 22 | servers with same root password for each one. Without any doubts about possible 23 | break-in. 24 | 25 | If attacker has user (non-root) access to the one server and try to repeat 26 | request to shadowd server and get actual hash during the hash TTL period (one 27 | day by default), then shadowd will give him another unique hash entry. 28 | Actually, **shadowd** can give only two unique hash entries for hash TTL period 29 | for one client, first hash entry may be received only for first request per 30 | hash TTL period, all other requests will be served by another hash. 31 | 32 | If attacker has root access to the one server and will try to brute-force 33 | hash entry for root from `/etc/shadow`, it will not give him any access for 34 | other servers with same password, because **shadowd** will give different 35 | hashes for each server. 36 | 37 | If attacker will gain root access to the **shadowd** server (worst-case 38 | scenario), it's will be very time-consuming to brute-force thousands of hashes 39 | without any knowledge about which server is using specific hash entry. 40 | 41 | **shadowd** can act as SSH keys publisher too. 42 | 43 | **shadowd** can also configure with passwords containers or any other type of 44 | nodes in your infrastructure. 45 | 46 | REST API is used for communication between server and client. 47 | 48 | ![Plan](http://i.imgur.com/gfDy0a5.png) 49 | 50 | ## FAQ 51 | 52 | ### Why not LDAP/AD/SSSD/Whatever? 53 | 54 | LDAP/AD/SSSD is monstrous software which requires massive configuration. It's 55 | far from just installing package to make it work. LDAP by itself just ugly, 56 | it's not so "lightweight" as it supposed to be from it's name. 57 | 58 | **shadowd** is very easy to install and do not even have configuration file. 59 | 60 | In comparison to LDAP, **shadowd** is not a remote authorisation provider, so 61 | when network or shadowd host will go down, you still will be able to login on 62 | concrete hosts configured with **shadowc**, because all shadow hashes stored 63 | locally on concrete host. 64 | 65 | ### Isn't attacker will brute-force actual password faster instead of finding a collision in SHA? 66 | 67 | It is possible indeed, but having sufficiently long password like 68 | `thecorrecthorsebatterystapleinspace` (https://xkcd.com/936/) will neglect this 69 | probability. 70 | 71 | ### What is actual use case for this? 72 | 73 | Local authentication as well as password authentication should be always 74 | possible for ops engineers. If something wrong goes with LDAP or any other 75 | remote authentication agent, you will blame that day you integrated it into 76 | your environment. 77 | 78 | There is no way of configuring local root (like, available only through IPMI) 79 | on the frontend load-balancing servers through remote authorisation agent. 80 | 81 | **shadowd** can be safely used for setting root password even on load-balancing 82 | servers. 83 | 84 | ## shadowd configuration 85 | 86 | 1. [Generate hash tables](#hash-tables) 87 | 2. [Generate SSL certificates](#ssl-certificates) 88 | 3. [Start shadowd](#start-shadowd) 89 | 4. [Adding SSH keys](#adding-ssh-keys) 90 | 5. [REST API](#rest-api) 91 | 92 | ### Hash Tables 93 | 94 | For generating hash table you should run: 95 | ``` 96 | shadowd [options] -G [-n ] [-a ] 97 | ``` 98 | **shadowd** will prompt for a password for specified user token, and after that 99 | will generate hash table with 2048 hashed entries of specified password, hash 100 | table size can be specified via flag `-n ` `sha256` will be used as 101 | default hashing algorithm, but `sha512` can be used via `-a sha512` flag. 102 | 103 | Actually, user token can be same as login, but if you want to use several 104 | passwords for same username on different servers, you should specify `` 105 | as `/` where `` it is name of role (`production` or `testing` 106 | for example). 107 | 108 | Already running instance of **shadowd** do not require reload to serve newly 109 | generated hash-tables. 110 | 111 | ![loading message](http://i.imgur.com/fbKYTMX.gif) 112 | 113 | ### SSL certificates 114 | 115 | Assume that attacker gained access to your servers, then he can wait for next 116 | password update and do man-in-the-middle attack, afterwards passwords on 117 | servers will be changed on his password and he will get more access to the 118 | servers. 119 | 120 | For solving that problem one should use SSL certificates, which confirms 121 | authority of the login distribution server. 122 | 123 | For generating SSL certificates you should have trusted host (shadowd server 124 | DNS name) or trusted ip address, if you will use localhost for shadowd 125 | server, you can skip this step and shadowd will automatically specify current 126 | hostname and ip address as trusted, in other cases you should pass options for 127 | setting trusted hosts/addresses of shadowd. 128 | 129 | Possible Options: 130 | - `-h --host ` - set specified host as trusted. (default: current 131 | hostname) 132 | - `-i --address ` - set specified ip address as trusted. (default: current 133 | ip address) 134 | - `-d --till ` - set time certicicate valid till (default: current 135 | date plus one year). 136 | - `-b --bytes ` - set specified length of RSA key. (default: 2048) 137 | 138 | And for all of this you should run one command: 139 | ``` 140 | shadowd [options] -C [-h ...] [-i ...] [-d ] [-b ] 141 | ``` 142 | 143 | Afterwards, `cert.pem` and `key.pem` will be stored in 144 | `/var/shadowd/cert/` directory, which location can be changed through flag 145 | `-c `. 146 | 147 | Since client needs certificate, you should copy `cert.pem` on 148 | server with client to `/etc/shadowc/cert.pem`. 149 | 150 | **shadowd** will generate certificate with default parameters (can be seen in 151 | program usage) on it's first run. 152 | 153 | ### Start shadowd 154 | 155 | As mentioned earlier, shadowd uses REST API, by default listening on `:443`, 156 | but you can set specified address and port through passing argument 157 | `-L `: 158 | 159 | ``` 160 | shadowd [options] -L [-s