Epithet, v2, Briefly - Part 1 28 Nov 2025
I last talked about epithet two years ago, in 2023, when I started kicking around ideas to make it general purpose. I'm pleased with how it's turned out, so I want to start talking about some of the design decisions. I dare not document it yet, for fear someone may use it, but that's probably coming.
Even though it never had a proper 1.0, I am calling this v2 in my head as it is not at all compatible with the previous iteration, and is so much better. The big pieces are the same shape: an Agent, a CA, and a Policy service:
Agent
The agent handles authenticating you, obtaining certificates, and deciding which certificate to use for a given connection. It uses a small policy protocol to match certificates against connections based on target host and remote username. Authentication is handled by a plugin system, so you can have arbitrary authenticators. There's a built-in OIDC authenticator, and I imagine I'll add SAML at some point.
Policy Service
The Policy Service is the brains of the system. It receives authentication and connection information and decides (1) whether to issue a cert, (2) which principals to put on the cert, (3) the cert parameters (expiration, extensions, etc), and (4) a matching policy telling the agent which connections this cert can be used for.
CA
The CA has access to the CA private key, so I want it to be as simple as possible. As such it delegates decision making about certificates to the policy service and merely issues certs as instructed.
So what's changed and why?
That looks pretty similar to two years ago, but there are a few key changes I'll dive into over a few posts. Today, the big reasons for the changes:
- I want to be able to issue a certificate which allows someone (or some process) to ssh into a specific host as a specific user only, rather than a more wide-open certificate with all of the user's principals. Imagine, if you will, a piece of deployment automation. We want it to be able to ssh as
deploy@prod-*.example.comfor the next ten minutes. Allowing this should be triggered by a workflow -- either automatically by inspecting if it should be deploying right now, or via a human workflow such as a response in Slack, and so on. - I don't want to bake in assumptions about how identities, principals, and hosts map to each other. The unix-group style set of principals from the previous version is reasonable for many use cases, but not all. It's overkill for simple cases (my personal stuff) and not powerful enough for the hard cases (see above).
- I want to coexist better with non-epithet ssh scenarios. Previously users had to carefully craft
Matchblocks in their ssh config to only use epithet when appropriate. Worse, if the CA or policy server was down, users would need to edit their ssh config to use a fallback. The last thing you want to do in a SEV is edit ssh config.