The mesh certifies names inside it
08-connectivity keeps two authorities apart on purpose: a public one for names the outside world reaches, and the mesh's own for names only the mesh knows. Nothing implemented the second, so anything between machines was plaintext or trust-on-first-use — which the design refuses everywhere else. A node now generates a fourth key at enrolment and reports the public half. A fourth, because a key used for two purposes is one rotation away from breaking the other: the identity key signs messages to the mesh and would do for TLS, and reusing it would mean rotating a node's identity every time its certificate is replaced. **Nothing secret travels and nothing is sealed.** A certificate authority says "this name belongs to the holder of this key", so the mesh signs a public half it cannot use, and the certificate it issues is public. A module asks for one and is given the certificate and, if it wants, the mesh's own — the private key is a path to a file the machine already has, the same arrangement the private network's key uses. Asserted by verifying rather than inspecting, because a certificate that parses and does not chain fails at the moment something connects: - what the mesh issues verifies against the mesh, for the name asked for - the name is in the subject alternative names, since a certificate carrying it only in the common name is refused by every modern client - it certifies the key the node generated and no other - another mesh's certificate does not verify, which is the whole point of two authorities being separate - the authority cannot sign another authority — one that could is one that can be delegated without anybody deciding to - two control planes starting together agree on one authority, or a mesh has certificates half its machines refuse Certificates last ten years, which is a choice: a short life needs something to renew it, and a renewal that fails silently is a mesh that stops trusting itself on a date nobody wrote down. What makes one replaceable is that the mesh reissues on demand, not that it expires.
This commit is contained in:
@@ -212,6 +212,18 @@ type Manifest struct {
|
||||
// module, in a file anybody can read, for ever.
|
||||
Needs map[string]string `json:"needs,omitempty"`
|
||||
|
||||
// Certificate is where this module wants a certificate for its machine's name inside the
|
||||
// mesh, and where the key that goes with it can be found.
|
||||
//
|
||||
// **The key is named, not delivered.** The node generated it at enrolment and keeps it; the
|
||||
// mesh only ever signs the public half. So what arrives is a certificate, which is public,
|
||||
// and a path to a file the machine already has.
|
||||
//
|
||||
// Two authorities are kept apart on purpose (novox/hq 08-connectivity): this is the mesh's,
|
||||
// for names only the mesh knows. A name the outside world reaches is a different authority
|
||||
// and a different problem.
|
||||
Certificate *Certificate `json:"certificate,omitempty"`
|
||||
|
||||
// Grants is a directory this module wants the credentials of its consumers written into, per
|
||||
// provision it offers — one file per consumer, named for it, holding the value alone.
|
||||
//
|
||||
@@ -262,6 +274,19 @@ const (
|
||||
ArtifactUpstream = "upstream"
|
||||
)
|
||||
|
||||
// Certificate says where a module wants what the mesh issued for its machine.
|
||||
type Certificate struct {
|
||||
// Into is where the certificate is written.
|
||||
Into string `json:"into"`
|
||||
// Authority is where the mesh's own certificate is written, so something connecting to this
|
||||
// machine can be told what to believe. Optional: a module that only serves does not need it.
|
||||
Authority string `json:"authority,omitempty"`
|
||||
}
|
||||
|
||||
// CertificateID and AuthorityID are the resource identities of what the mesh issued.
|
||||
func CertificateID() string { return "certificate" }
|
||||
func AuthorityID() string { return "certificate-authority" }
|
||||
|
||||
// NeedID is the resource identity of the file a module's own secret lands in.
|
||||
func NeedID(name string) string { return "needs-" + name }
|
||||
|
||||
@@ -404,6 +429,17 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
||||
"%s binds %q and does not require it", m.Module, to))
|
||||
}
|
||||
}
|
||||
if c := m.Certificate; c != nil {
|
||||
if !strings.HasPrefix(c.Into, "/") {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s wants its certificate at %q, which is not an absolute path", m.Module, c.Into))
|
||||
}
|
||||
if c.Authority != "" && !strings.HasPrefix(c.Authority, "/") {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s wants the authority at %q, which is not an absolute path",
|
||||
m.Module, c.Authority))
|
||||
}
|
||||
}
|
||||
for name, where := range m.Needs {
|
||||
if !strings.HasPrefix(where, "/") {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
|
||||
Reference in New Issue
Block a user