{"id":36922827,"url":"https://github.com/danielemery/keys","last_synced_at":"2026-02-28T22:09:09.794Z","repository":{"id":60262770,"uuid":"436419365","full_name":"danielemery/keys","owner":"danielemery","description":"Static web app acting as a source of truth for public ssh keys, known hosts and pgp keys","archived":false,"fork":false,"pushed_at":"2025-12-20T15:04:38.000Z","size":251,"stargazers_count":2,"open_issues_count":13,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-22T17:55:27.516Z","etag":null,"topics":["keys","security","ssh"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/danielemery.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-12-08T23:12:38.000Z","updated_at":"2025-06-24T06:32:06.000Z","dependencies_parsed_at":"2026-01-06T15:00:35.795Z","dependency_job_id":null,"html_url":"https://github.com/danielemery/keys","commit_stats":null,"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"purl":"pkg:github/danielemery/keys","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielemery%2Fkeys","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielemery%2Fkeys/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielemery%2Fkeys/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielemery%2Fkeys/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielemery","download_url":"https://codeload.github.com/danielemery/keys/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielemery%2Fkeys/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28342405,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T16:14:38.383Z","status":"ssl_error","status_checked_at":"2026-01-12T16:14:34.289Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["keys","security","ssh"],"created_at":"2026-01-12T16:24:19.439Z","updated_at":"2026-01-12T16:24:19.486Z","avatar_url":"https://github.com/danielemery.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Keys\n\nSimple repository to manage and distribute ssh keys.\n\nTo see a production implementation of this app feel free to visit\nhttps://keys.demery.net/keys\n\nPublic keys are provided in a configuration file at application start. The\napplication has no persistence layer and is stateless.\n\n[![codecov](https://codecov.io/gh/danielemery/keys/branch/main/graph/badge.svg?token=3F3EN3UY21)](https://codecov.io/gh/danielemery/keys)\n\n## Example Usage\n\nTypically the purpose of keys is to override the `authorized_keys` file for a\nmachine. This is currently done with manual curl commands, and not typically\nautomated with a CRON due to the risk of losing access to machines as a result\nof the service being down or misconfigured.\n\nIn the future ([#24](https://github.com/danielemery/keys/issues/24)) a cli tool\nwill be provided to safely manage the `authorized_keys` file with guards in\nplace to prevent loss of access.\n\n### Get all listed keys\n\n```sh\ncurl \"https://keys.demery.net/keys\"\n```\n\n### Update authorized keys file\n\n_Get keys for the `demery` user with the `oak` tag and excluding the `disabled`\ntag and override the `authorized_keys` file with them_\n\n```sh\n# Consider backup first\ncp ~/.ssh/authorized_keys ~/.ssh/authorized_keys.`date '+%Y-%m-%d__%H_%M_%S'`.backup\n# Override file with the matching keys\ncurl \"https://keys.demery.net/keys?user=demery\u0026allOf=oak\u0026noneOf=disabled\" \u003e ~/.ssh/authorized_keys\n# Check that they keys were updated with what you expected\ncat ~/.ssh/authorized_keys\n```\n\n### Update known hosts file\n\n_Replaces the `known_hosts` file with the hosts in your keys instance_\n\n```sh\n# Consider backup first\ncp ~/.ssh/known_hosts ~/.ssh/known_hosts.`date '+%Y-%m-%d__%H_%M_%S'`.backup\n# Override file with the hosts from the keys instance\ncurl http://localhost:8000/known_hosts \u003e ~/.ssh/known_hosts\n```\n\n## JSON API\n\nAll endpoints also support JSON responses. You can request JSON by setting the\n`Accept` header to `application/json`.\n\n### Get PGP keys as JSON\n\n```sh\n# Get list of all PGP keys in JSON format\ncurl -H \"Accept: application/json\" \"https://keys.demery.net/pgp\"\n\n# Get a specific PGP key in JSON format\ncurl -H \"Accept: application/json\" \"https://keys.demery.net/pgp/key-name\"\n```\n\n### Get SSH keys as JSON\n\n```sh\n# Get all SSH keys in JSON format\ncurl -H \"Accept: application/json\" \"https://keys.demery.net/keys\"\n\n# Get filtered SSH keys in JSON format\ncurl -H \"Accept: application/json\" \"https://keys.demery.net/keys?user=demery\u0026allOf=oak\u0026noneOf=disabled\"\n```\n\n### Get known hosts as JSON\n\n```sh\n# Get all known hosts in JSON format\ncurl -H \"Accept: application/json\" \"https://keys.demery.net/known_hosts\"\n```\n\n## Running / Installation\n\n### Configuration File\n\nRegardless of the method of deployment, the `keys` application requires a config\nyaml file containing the list of keys to be served. An example file can be found\nin `./examples/keys-config.yaml`.\n\nThe config file contains three main sections:\n\n- `ssh-keys`: A list of public ssh keys with the following fields:\n  - name: The name of the key (this will be used as the `@host` in the\n    `authorized_keys` file)\n  - key: The public key itself\n  - user: The user that the key should be associated with (this will be used as\n    the `user@host` in the `authorized_keys` file)\n  - tags: Optionally a list of tags that can be used to filter the keys\n- `pgp-keys`: A list of public pgp keys with the following fields:\n  - name: The name of the key (this will be used in the route and as the\n    filename if you download the key)\n  - key: The public key itself\n- `known-hosts`: A list of known hosts with the following fields:\n  - name: Optional name for the entry, it's not used in the `known_hosts` file\n    and is just for your records\n  - hosts: A list of hostnames or IPs that the key(s) should be associated with\n  - keys: A list of known keys that should be associated with the host, with the\n    following fields:\n    - type: The type of key (eg `ssh-rsa`)\n    - key: The public key itself\n    - comment: Optional comment for the entry (will be appended to the key in\n      the `known_hosts` file)\n    - revoked: Optional boolean to indicate that the key should be considered\n      revoked (adds the @revoked marker in the known hosts file)\n    - cert-authority: Optional boolean to indicate that the key is a certificate\n      authority (adds the @cert-authority marker in the known hosts file)\n\n### Helm\n\n#### Secret Creation\n\nThe recommended method of deploying the `keys` application is using the official\nhelm chart.\n\nBefore deployment, it's expected to have a secret created within the target\nnamespace that contains the environment variable configuration for the\napplication. This secret is expected to be named `keys-secret` but can be\noverriden by setting the `secrets.secretName` value in the helm chart.\n\nThe secret could be created using doppler (see\n[Doppler docs](./docs/DOPPLER.md)) or another secrets solution. Or by simply\ncreating the secret file manually (recommended for one-off tests).\n\nManual secret creation:\n\n1. Create a new namespace in your cluster for the test\n   ```sh\n   kubectl create namespace keys-test\n   ```\n2. Create the secret file\n   ```sh\n   kubectl create secret generic keys-secret \\\n     --from-literal=DOPPLER_ENVIRONMENT=local-helm-test\n   ```\n\n#### Chart Repository\n\nThe following assumes you have created the namespace and secrets as\n[described above](#secret-creation).\n\n```sh\nhelm repo add keys https://helm.demery.net\nhelm repo update\nhelm install -n keys-test --set version=v2.0.0 --set configFile.content=\"$(cat ./examples/keys-config.yaml)\" keys demery/keys\n```\n\n### Docker\n\nThe `keys` application is packaged in docker and the image can be found in the\n[Github registry](https://ghcr.io/danielemery/keys).\n\n_The inner container port is always **8000** unless overriden with the `PORT`\nenvironment variable. Note that port **80** cannot be used due to limitations of\nthe way Deno handles permissions._\n\n```sh\ndocker run \\\n  -p 8000:8000 \\\n  -v $(pwd)/examples/keys-config.yaml:/config.yaml \\\n  -e DOPPLER_ENVIRONMENT=local-docker \\\n  -e KEYS_VERSION=local-docker \\\n  ghcr.io/danielemery/keys:latest\n```\n\n#### Docker Compose\n\nWhen using docker instead of the helm chart it's recommended to use a\ndocker-compose file.\n\nAn example is provided in `examples/docker-compose.yaml` and can be run with the\nfollowing command:\n\n```sh\ndocker compose -f examples/docker-compose.yaml up\n```\n\n## Development\n\n### Running locally\n\nMake a copy of the `.env.example` file and rename it to `.env`. Review it's\ncontents and make any necessary changes.\n\n```sh\ncp .env.example .env\n```\n\nThen run the following command to start the server:\n\n```sh\ndeno run --env --allow-net --allow-env --allow-read main.ts\n```\n\n### Run tests\n\n```sh\ndeno test --allow-read\n```\n\n### Local Helm Chart\n\nIt can be useful to run the chart directly from the repository for testing or\nfor using the chart with a version that has not yet been published.\n\n1. Install the chart\n   ```sh\n   # Replace the version with the desired version. It will need to be a version that exists in the Github registry.\n   helm install -n keys-test --set version=v2.0.0 --set configFile.content=\"$(cat ./examples/keys-config.yaml)\" keys ./helm\n   ```\n2. Port forward to the service for testing\n   ```sh\n   kubectl -n keys-test port-forward svc/keys-svc 8000:80\n   ```\n\n### Local Docker Build\n\nIt can also be useful to build the docker image locally for testing of\n`Dockerfile` changes. This can be done with the following commands:\n\n```sh\ndocker build -t keys:local .\ndocker run \\\n  -p 8000:8000 \\\n  -v $(pwd)/examples/keys-config.yaml:/config.yaml \\\n  -e DOPPLER_ENVIRONMENT=local-docker \\\n  -e KEYS_VERSION=local-docker \\\n  keys:local\n```\n\n### Branch / Release strategy\n\n- Tags should follow [semantic versioning](https://semver.org/)\n- Most PRs should be made to `main` branch\n  - Patch and minor Tags will be created on the `main` branch eg\n    `v1.3.0`/`v1.3.1`\n  - Usually a tag and release will be performned after each merge to `main`, but\n    sometimes multiple PRs can be merged before a tag is created\n- Breaking change PRs should be made to the `next` branch\n  - Release candidate tags will be created on the `next` branch for the next\n    breaking change version (eg `v2.0.0-rc.1`)\n  - Once the release candidate has been validated and is ready to be released,\n    the `next` branch will be merged into `main` and a new tag will be created\n    on `main` (eg `v2.0.0`)\n  - The `next` branch should only be merged to `main` using the fast-forward\n    merge strategy\n  - Each breaking change version should have it's progress tracked using a\n    milestone on GitHub\n- Breaking change PRs should always be labelled as such (and the future a rule\n  will be created not allow them to be directly merged into `main`)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielemery%2Fkeys","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielemery%2Fkeys","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielemery%2Fkeys/lists"}