{"id":13551214,"url":"https://github.com/reconquest/shadowd","last_synced_at":"2025-09-06T09:39:09.813Z","repository":{"id":27890800,"uuid":"31382298","full_name":"reconquest/shadowd","owner":"reconquest","description":"Secure login distribution service","archived":false,"fork":false,"pushed_at":"2018-05-25T16:01:26.000Z","size":84,"stargazers_count":358,"open_issues_count":1,"forks_count":29,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-02T07:09:19.693Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/reconquest.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-02-26T18:36:07.000Z","updated_at":"2025-03-21T23:58:40.000Z","dependencies_parsed_at":"2022-09-11T15:51:25.156Z","dependency_job_id":null,"html_url":"https://github.com/reconquest/shadowd","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reconquest%2Fshadowd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reconquest%2Fshadowd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reconquest%2Fshadowd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reconquest%2Fshadowd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reconquest","download_url":"https://codeload.github.com/reconquest/shadowd/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248008630,"owners_count":21032556,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T12:01:44.260Z","updated_at":"2025-04-09T09:07:47.163Z","avatar_url":"https://github.com/reconquest.png","language":"Go","readme":"# shadowd\n\n![shadow horse](https://cloud.githubusercontent.com/assets/8445924/9289438/97f8a2e8-435f-11e5-853c-255a7fe22d08.png)\n\n**shadowd** is the secure login distribution service, which consists of two\nparts: server and client.\n\nIn a typical server configuration case you should manually update the\n`/etc/shadow` and copy it on all servers (or via automatic configuration\nsystem); afterwards each server will have same hash in the `/etc/shadow`.\nSupposed that attacker successfully gained access to one of your servers and\nfound collision to that single hash *attacker actually got access to all\nservers with that hash*.\n\n**shadowd** is summoned to solve that obscure problem.\n\n**shadowd solution** is to generate hash tables for specified passwords mixed\nwith random salt for specified users and guarantee that a client receive unique\nhash.\n\nOne **shadowd** instance can be used for securely instanciate thousands of\nservers with same root password for each one. Without any doubts about possible\nbreak-in.\n\nIf attacker has user (non-root) access to the one server and try to repeat\nrequest to shadowd server and get actual hash during the hash TTL period (one\nday by default), then shadowd will give him another unique hash entry.\nActually, **shadowd** can give only two unique hash entries for hash TTL period\nfor one client, first hash entry may be received only for first request per\nhash TTL period, all other requests will be served by another hash.\n\nIf attacker has root access to the one server and will try to brute-force\nhash entry for root from `/etc/shadow`, it will not give him any access for\nother servers with same password, because **shadowd** will give different\nhashes for each server.\n\nIf attacker will gain root access to the **shadowd** server (worst-case\nscenario), it's will be very time-consuming to brute-force thousands of hashes\nwithout any knowledge about which server is using specific hash entry.\n\n**shadowd** can act as SSH keys publisher too.\n\n**shadowd** can also configure with passwords containers or any other type of\nnodes in your infrastructure.\n\nREST API is used for communication between server and client.\n\n![Plan](http://i.imgur.com/gfDy0a5.png)\n\n## FAQ\n\n### Why not LDAP/AD/SSSD/Whatever?\n\nLDAP/AD/SSSD is monstrous software which requires massive configuration. It's\nfar from just installing package to make it work. LDAP by itself just ugly,\nit's not so \"lightweight\" as it supposed to be from it's name.\n\n**shadowd** is very easy to install and do not even have configuration file.\n\nIn comparison to LDAP, **shadowd** is not a remote authorisation provider, so\nwhen network or shadowd host will go down, you still will be able to login on\nconcrete hosts configured with **shadowc**, because all shadow hashes stored\nlocally on concrete host.\n\n### Isn't attacker will brute-force actual password faster instead of finding a collision in SHA?\n\nIt is possible indeed, but having sufficiently long password like\n`thecorrecthorsebatterystapleinspace` (https://xkcd.com/936/) will neglect this\nprobability.\n\n### What is actual use case for this?\n\nLocal authentication as well as password authentication should be always\npossible for ops engineers. If something wrong goes with LDAP or any other\nremote authentication agent, you will blame that day you integrated it into\nyour environment.\n\nThere is no way of configuring local root (like, available only through IPMI)\non the frontend load-balancing servers through remote authorisation agent.\n\n**shadowd** can be safely used for setting root password even on load-balancing\nservers.\n\n## shadowd configuration\n\n1. [Generate hash tables](#hash-tables)\n2. [Generate SSL certificates](#ssl-certificates)\n3. [Start shadowd](#start-shadowd)\n4. [Adding SSH keys](#adding-ssh-keys)\n5. [REST API](#rest-api)\n\n### Hash Tables\n\nFor generating hash table you should run:\n```\nshadowd [options] -G \u003ctoken\u003e [-n \u003csize\u003e] [-a \u003calgo\u003e]\n```\n**shadowd** will prompt for a password for specified user token, and after that\nwill generate hash table with 2048 hashed entries of specified password, hash\ntable size can be specified via flag `-n \u003csize\u003e` `sha256` will be used as\ndefault hashing algorithm, but `sha512` can be used via `-a sha512` flag.\n\nActually, user token can be same as login, but if you want to use several\npasswords for same username on different servers, you should specify `\u003ctoken\u003e`\nas `\u003cpool\u003e/\u003clogin\u003e` where `\u003cpool\u003e` it is name of role (`production` or `testing`\nfor example).\n\nAlready running instance of **shadowd** do not require reload to serve newly\ngenerated hash-tables.\n\n![loading message](http://i.imgur.com/fbKYTMX.gif)\n\n### SSL certificates\n\nAssume that attacker gained access to your servers, then he can wait for next\npassword update and do man-in-the-middle attack, afterwards passwords on\nservers will be changed on his password and he will get more access to the\nservers.\n\nFor solving that problem one should use SSL certificates, which confirms\nauthority of the login distribution server.\n\nFor generating SSL certificates you should have trusted host (shadowd server\nDNS name) or trusted ip address, if you will use localhost for shadowd\nserver, you can skip this step and shadowd will automatically specify current\nhostname and ip address as trusted, in other cases you should pass options for\nsetting trusted hosts/addresses of shadowd.\n\nPossible Options:\n- `-h --host \u003chost\u003e` - set specified host as trusted. (default: current\n    hostname)\n- `-i --address \u003cip\u003e` - set specified ip address as trusted. (default: current\n    ip address)\n- `-d --till \u003cdate\u003e` - set time certicicate valid till (default: current\n    date plus one year).\n- `-b --bytes \u003clength\u003e` - set specified length of RSA key. (default: 2048)\n\nAnd for all of this you should run one command:\n```\nshadowd [options] -C [-h \u003chost\u003e...] [-i \u003cip\u003e...] [-d \u003cdate\u003e] [-b \u003clength\u003e]\n```\n\nAfterwards, `cert.pem` and `key.pem` will be stored in\n`/var/shadowd/cert/` directory, which location can be changed through flag\n`-c \u003ccert_dir\u003e`.\n\nSince client needs certificate, you should copy `cert.pem` on\nserver with client to `/etc/shadowc/cert.pem`.\n\n**shadowd** will generate certificate with default parameters (can be seen in\nprogram usage) on it's first run.\n\n### Start shadowd\n\nAs mentioned earlier, shadowd uses REST API, by default listening on `:443`,\nbut you can set specified address and port through passing argument\n`-L \u003clisten\u003e`:\n\n```\nshadowd [options] -L \u003clisten\u003e [-s \u003ctime\u003e]\n```\n\nFor setting hash TTL duration you should pass `-s \u003ctime\u003e` argument, by\ndefault hash TTL is `24h`.\n\nTTL is amount of time after which shadowd will serve different unique pair of\nhash entries to the same requesting client.\n\n#### General options:\n\n- `-c -certs \u003cdir\u003e` - use specified directory for storing and reading\n    certificates.\n- `-t --tables \u003cdir\u003e` - use specified directory for storing and reading\n    hash tables. (default: /var/shadowd/ht/)\n- `-k --keys \u003cdir\u003e` - use specified dir for reading ssh-keys.\n    (default: /var/shadowd/ssh/).\n\nSuccess, you have configured server, but you need to configure client, for this\nyou should see\n[documentation here](https://github.com/reconquest/shadowc).\n\n### Adding SSH keys\n\n**shadowd** can act as public ssh keys distribution service. Keys can be added\nper token by using command:\n\n```\nshadowd -K \u003ctoken\u003e\n```\n\nAfter that command **shadowd** will wait for public SSH key to be entered on\nstdin.  Then, specified key will be added to keys list, which is stored under\nthe directory, set by `-k` flag (`/var/shadowd/ssh/` by default).\n\nOptionally, key file can be truncated by using flag `-r --truncate` to the\nstandard `-K` invocation.\n\n**shadowd** will serve that keys by HTTP, as mentioned in following section.\n\n### Scalability\n\n**shadowd** can work in multiple instance mode, but it requires to use external\nstorage \u0026ndash; MongoDB.\n\n**shadowd** will store/read shadow entries and public ssh key from mongodb, so\nall of your need is configure mongodb cluster, setup replication and place\nshadowd configuration file using following syntax:\n\n```\n[backend]\nuse = \"mongodb\"\ndsn = \"mongodb://[user[:password]@]host[,[user2[:password2]@]host2]/dbname\"\n```\n\n**shadowd**'s' configuration file can be specified using `-f --config \u003cpath\u003e`\nflag.\n\n### REST API\n\n**shadowd** offers following REST API:\n\n* `/t/\u003ctoken\u003e`, where token can be any string, possibly containing slashes.\n  Most common interpretation for `\u003ctoken\u003e` is `\u003cpool\u003e/\u003cusername\u003e`, e.g.\n  `dev/v.pupkin`.\n\n  `GET` on this URL will return unique hash for specified `\u003ctoken\u003e` from\n  pre-generated via `shadowd -G` hash-table. The first requested hash\n  guaranteed to be unique among different hosts, The second and later `GET`\n  requests will return same hash again and again until `\u003chash_ttl\u003e` expires.\n  `\u003chash_ttl\u003e` is configured on server by `-a` flag. Working in that way\n  **shadowd** offers secure way of transmitting one hash only once, and\n  legitimate client (e.g. **shadowc**) can always be sure that hash, obtained\n  from **shadowd**, has not been transferred to someone else on that host.\n\n* `/ssh/\u003ctoken\u003e`, where `\u003ctoken\u003e` is same as above.\n\n  `GET` on this URL will return SSH keys, that has been added by `shadowd -K`\n  command in `authorized_keys` format (e.g. key per line).\n\n  No special security restrictions apply on that requests.\n\n* `/v/\u003ctoken\u003e/\u003chash\u003e`, where `\u003ctoken\u003e` is same as above, `\u003chash\u003e` is\n    hash of a password, if `\u003ctoken\u003e` contains `/` (`\u003cpool\u003e/\u003cusername\u003e`) then\n    the last part of URI will be used as `\u003chash\u003e`.\n    i.e.\n    `dev/v.pupkin/$5$NqjWijImgEOapAJw$NL5K7g8SwMferONwiskz6bcluwlO7zbu3V/ZyLzavZD`\n\n  `GET` on this URL will return HTTP status `200 OK` if specified hash exists\n  in hash table for specified token, in other case, `404 Not\n  Found` will be returned.\n\n  No special security restrictions apply on that requests.\n","funding_links":[],"categories":["Go","others"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freconquest%2Fshadowd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freconquest%2Fshadowd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freconquest%2Fshadowd/lists"}