Sharkey is a service for managing certificates for use by OpenSSH.
Sharkey has a client component and a server component. The server is responsible for issuing signed host certificates, the client is responsible for installing host certificates on machines. Sharkey builds on the trust relationships of your existing X.509 PKI to manage trusted SSH certificates. Existing X.509 certificates can be minted into SSH certificates, so you don't have to maintain two separate PKI hierarchies.
Check out the repository, and build client/server:
go build -o sharkey-client ./client go build -o sharkey-server ./server
The server component accepts requests and issues short lived host certificates.
Clients send their public key to the server (via TLS with mutual authentication) periodically. The server authenticates the client by checking that its certificate is valid for the requested hostname. If everything looks good, the server will take the public key in the request and issue an OpenSSH host certificate for the requested hostname.
A log of all issued certificates is stored in a database. The server can generate a
known_hosts file from the issuance log if required.
usage: sharkey-server --config=CONFIG [<flags>] <command> [<args> ...] Certificate issuer of the ssh-ca system. Flags: --help Show context-sensitive help (also try --help-long and --help-man). --config=CONFIG Path to config file for server. --version Show application version. Commands: help [<command>...] Show help. start Run the sharkey server. migrate [<flags>] Set up database/run migrations.
# SQLite database # --- db: address: /path/to/sharkey.db type: sqlite # MySQL database # --- # db: # username: root # password: password # address: hostname:port # schema: ssh_ca # type: mysql # tls: # MySQL TLS config (optional) # ca: /path/to/mysql-ca-bundle.pem # cert: /path/to/mysql-client-cert.pem # MySQL client cert # key: /path/to/mysql-client-cert-key.pem # MySQL client cert key # Server listening address listen_addr: "0.0.0.0:8080" # TLS config for serving requests # --- tls: ca: /path/to/ca-bundle.pem cert: /path/to/server-certificate.pem key: /path/to/server-certificate-key.pem # Signing key (from ssh-keygen) signing_key: /path/to/ca-signing-key # Lifetime/validity duration for generated host certificates host_cert_duration: 168h # Lifetime/validity duration for generated user certificates user_cert_duration: 24h # Optional suffix to strip from client hostnames when generating certificates. # This is useful if all your machines have a common TLD/domain, and you want to # include an alias in the generated certificate that doesn't include that suffix. # Leave empty to disable strip_suffix: ".example.com" # Optional set of aliases for hosts. If a hostname matches an alias entry, the # listed principals will be added to its certificate. This is useful if you have # special hosts that are accessed via CNAME records. aliases: "host.example.com": - "alias1.example.com" - "alias2.example.com" # Optional set of extra entries to provide to clients when they fetch a known_hosts # file. This is useful if you have externally-managed servers in your infrastructure # that you want to tell clients about, of if you want to add CA entries to the # known_hosts file. extra_known_hosts: - "@cert-authority *.example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBwhA8rKPESjDy4iqTlkBqUlBU2xjwtmFUHY6cutA9TYbB5H/mjxzUpnSNw/HyFWNpysjTSQtHWWBdJdJGU/0aDgFUwbduHeDFxviGVSkOxm2AYn7XJopzITZRqmAmsYXHUBa75RQb+UgIG7EpCoi8hF4ItJV+TT777j1irkXwlMmeDiJEaA+7bPNdUdGw8zRbk0CyeotYVD0griRtkXdfgnQAu+DvBwOuW/uiZaPz/rAVjt4b9fmp6pcFKI3RsBqqn5tQVhKCPVuSwqvIQ7CTVkMClYovlH1/zGe8PG1DHbM9irP98S5j3mVD9W5v3QILpsg24RIS14M8pLarlD6t [email protected]" # User certs are issued to users who connect through an authenticating proxy # That user should connect with a user certificate and set the username # in a header. auth_proxy: # Hostname is validated against the incoming user certificate hostname: proxy.example.com # The HTTP header containing the username username_header: X-Forwarded-User # Optional settings related to SSH ssh: # List of extensions that should be set on the user certificate (default is no extensions) user_cert_extensions: - "permit-X11-forwarding" - "permit-agent-forwarding" - "permit-port-forwarding" - "permit-pty" - "permit-user-rc"
A signing key for generating host certificates can be generated with
Sharkey supports both SQLite and MySQL. There is a built-in command in the server binary to manage migrations (based on goose).
To run migrations on a configured database:
# SQLite ./sharkey-server --config=[CONFIG] migrate --migrations=db/sqlite # MySQL ./sharkey-server --config=[CONFIG] migrate --migrations=db/mysql
The client component periodically requests a new host certificate from the server and installs it on the machine.
The client will use a TLS client certificate to make a connection to the server and authenticate itself. This assumes that there is a long-lived certificate and key installed on each machine that uses the client. We then periodically read the host key for the locally running OpenSSH (
host_key), send it to the server, and retrieve a signed host certificate based on that key. The signed host certificate is then installed on the machine (
usage: sharkey-client --config=CONFIG [<flags>] Flags: --help Show context-sensitive help (also try --help-long and --help-man). --config=CONFIG Path to yaml config file for setup --version Show application version.
# Server address request_addr: "https://sharkey-server.example:8080" # TLS config for making requests # --- tls: ca: /path/to/ca-bundle.pem cert: /path/to/client-certificate.pem key: /path/to/client-certificate-key.pem # List of host keys for OpenSSH server host_keys: # Here, 'key' is the public key, and 'cert' is where to install the signed cert - plain: "/etc/ssh/ssh_host_rsa_key.pub" signed: "/etc/ssh/ssh_host_rsa_key-cert.pub" # You can specify multiple host keys (e.g. if you have both RSA, ED25519 keys) - plain: "/etc/ssh/ssh_host_ed25519_key.pub" signed: "/etc/ssh/ssh_host_ed25519_key-cert.pub" # Where to install the known_hosts file known_hosts: /etc/ssh/known_hosts # If set to true, only install authorities in known_hosts file (ignore other machine's host keys). known_hosts_authorities_only: false # How often to refresh/request new certificate sleep: "24h" # Path to sudo binary on client host # Uses sudo to write known_hosts and signed_cert.pub if this field specified sudo: "/usr/bin/sudo" # Command to restart ssh daemon for the host # If sudo is set as well, this command will be prefixed with 'sudo' ssh_reload: ["service", "ssh", "restart"]
OpenSSH will have to be configured to read the signed host certificate (this is with the
HostCertificate config option in
sshd_config). If the signed host certificate is missing from disk, OpenSSH will fall back to TOFU with the default host key. Therefore, it should always be safe to configure a host certificate; even if the Sharkey client fails you can still SSH into your machine.
For a user to SSH into an openssh server, they can present a certificate, which should have a principal matching their username. Sharkey outsources identifying users to an SSO proxy. That proxy needs to connect to sharkey over mTLS. You can configure the DNS SAN that should appear on the server's client cert (eg, proxy.example.com) and the HTTP header it sets the username to (eg, X-Forwarded-User). See example configs.
No client helper is included with Sharkey at this time, so you have to set up a script yourself at this time to enroll the user.
Testing looks something like this:
curl --cert proxy.crt --key proxy.key https://localhost:8080/enroll_user -H "X-Forwarded-User: bob" -d @~/.ssh/bob.pub
But in production use you'd expect it more like
curl <auth to your proxy> https://ssoproxy.example.com/enroll_user -d @~/.ssh/bob.pub