Unify trunk on main: initialization → main #3
+14
-1
@@ -108,7 +108,7 @@ func parseArgs(args []string) (string, options, error) {
|
|||||||
set.StringVar(&opts.state, "state", opts.state, "where this node keeps what it knows")
|
set.StringVar(&opts.state, "state", opts.state, "where this node keeps what it knows")
|
||||||
set.BoolVar(&opts.dryRun, "dry-run", false, "read and check the declaration, change nothing")
|
set.BoolVar(&opts.dryRun, "dry-run", false, "read and check the declaration, change nothing")
|
||||||
set.StringVar(&opts.token, "token", "", "enrol: the one-time token, carried here by a person")
|
set.StringVar(&opts.token, "token", "", "enrol: the one-time token, carried here by a person")
|
||||||
set.StringVar(&opts.nodeName, "name", "", "enrol: what this machine is called in the mesh")
|
set.StringVar(&opts.nodeName, "name", "", "enrol: override the name the token carries")
|
||||||
|
|
||||||
// Parsed in a loop, because the standard library stops at the FIRST non-flag argument.
|
// Parsed in a loop, because the standard library stops at the FIRST non-flag argument.
|
||||||
// `mesh-host inventory --json` hit that once, and taking the subcommand off the front
|
// `mesh-host inventory --json` hit that once, and taking the subcommand off the front
|
||||||
@@ -405,6 +405,19 @@ func enrol(ctx context.Context, opts options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The name comes from the token, because the node cannot work it out: the broker account it
|
||||||
|
// authenticates as is named after it, and that account exists before this machine has been
|
||||||
|
// told anything. --name remains for a token issued before the name travelled in one, and
|
||||||
|
// saying so beats a connection refused with an empty username — which is what this was.
|
||||||
|
if strings.TrimSpace(*name) == "" {
|
||||||
|
*name = token.Node
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(*name) == "" {
|
||||||
|
return errors.New(
|
||||||
|
"this token does not say what the mesh calls this machine, and no --name was given. " +
|
||||||
|
"A token issued by a current control plane carries the name")
|
||||||
|
}
|
||||||
|
|
||||||
// Before anything else: an already-enrolled machine must not quietly acquire a second
|
// Before anything else: an already-enrolled machine must not quietly acquire a second
|
||||||
// identity. The mesh believes the first one, and re-enrolling is a deliberate act that
|
// identity. The mesh believes the first one, and re-enrolling is a deliberate act that
|
||||||
// starts with a person issuing a new token for that node record.
|
// starts with a person issuing a new token for that node record.
|
||||||
|
|||||||
+12
-8
@@ -2,20 +2,24 @@
|
|||||||
|
|
||||||
## `substrate-first-node.lock`
|
## `substrate-first-node.lock`
|
||||||
|
|
||||||
What a machine must be before a mesh exists — steps 0 to 4 of the bootstrap in
|
What a machine must be before a mesh exists — the bootstrap in
|
||||||
[novox/hq `07-the-substrate.md`](https://git.novox.be/novox/hq):
|
[novox/hq `07-the-substrate.md`](https://git.novox.be/novox/hq), whole:
|
||||||
|
|
||||||
```
|
```
|
||||||
0 a container runtime
|
0 a container runtime
|
||||||
1 the store runs
|
1 the store runs
|
||||||
2 a database per context one today, `inventory`
|
2 a database per context `inventory` and `identity`
|
||||||
3 that context's schema mesh-control migrate
|
3 those contexts' schemas mesh-control migrate
|
||||||
4 the broker runs
|
4 the broker runs with a certificate it generated itself
|
||||||
|
5 the control plane runs mesh-control serve
|
||||||
```
|
```
|
||||||
|
|
||||||
**It stops there, and the file says why.** Step 5 is a virtual host, a credential and a
|
**A machine that applies this is a mesh** — one node, with nothing joined to it yet, which is
|
||||||
certificate; step 6 is the control plane running. Nothing consumes any of them yet, and a bundle
|
exactly what the first node is (novox/hq ADR 0004). From here it hands out tokens and everything
|
||||||
whose last step cannot be checked is worse than a shorter one.
|
else joins the ordinary way.
|
||||||
|
|
||||||
|
This file said it stopped at step 4 for longer than that was true, which is its own small lesson:
|
||||||
|
a comment about what something does not do is a comment nobody updates.
|
||||||
|
|
||||||
Build a host carrying it:
|
Build a host carrying it:
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// substrate-first-node.lock — what a machine must be before a mesh exists.
|
// substrate-first-node.lock — what a machine must be before a mesh exists.
|
||||||
//
|
//
|
||||||
// Steps 0 to 5 of the bootstrap (novox/hq 03-DESIGN/01-to-be/07-the-substrate.md): a container
|
// The whole bootstrap (novox/hq 03-DESIGN/01-to-be/07-the-substrate.md): a container runtime, a
|
||||||
// runtime, a store, a database per context, that context's schema, and the broker.
|
// store, a database per context, those contexts' schemas, the broker, and the control plane
|
||||||
|
// running on top of them.
|
||||||
//
|
//
|
||||||
// It stops before step 6, where the control plane runs.
|
// It stopped before the control plane once, and this comment said so for longer than it was true.
|
||||||
//
|
//
|
||||||
// The broker generates its OWN certificate, in its own image, into a volume it then mounts read
|
// The broker generates its OWN certificate, in its own image, into a volume it then mounts read
|
||||||
// only. Self-signed, because at this moment there is no mesh to issue one and no public name to
|
// only. Self-signed, because at this moment there is no mesh to issue one and no public name to
|
||||||
|
|||||||
@@ -377,3 +377,22 @@ func TestASealingKeyOnDiskSurvivesATrailingNewline(t *testing.T) {
|
|||||||
t.Fatalf("a round trip through the disk changed the key")
|
t.Fatalf("a round trip through the disk changed the key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestATokenSaysWhatTheMeshCallsThisMachine(t *testing.T) {
|
||||||
|
// The node cannot work its own name out. The broker account it authenticates as is named
|
||||||
|
// after it and exists before this machine has been told anything — so without the name in the
|
||||||
|
// token, enrolment is a connection refused with an empty username, which names nothing about
|
||||||
|
// the cause. That is exactly how the first end-to-end raise went.
|
||||||
|
raw := base64.RawURLEncoding.EncodeToString([]byte(
|
||||||
|
`{"v":1,"node":"anchor","broker":"192.0.2.10:5671",` +
|
||||||
|
`"fingerprint":"sha256:` + strings.Repeat("ab", 32) + `",` +
|
||||||
|
`"signer":"` + base64.StdEncoding.EncodeToString(make([]byte, 32)) + `",` +
|
||||||
|
`"secret":"a-one-time-secret"}`))
|
||||||
|
token, err := ParseToken(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if token.Node != "anchor" {
|
||||||
|
t.Fatalf("the name did not survive the token: %q", token.Node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,14 @@ import (
|
|||||||
// so they are held together by a test on each side asserting the exact field names rather than by
|
// so they are held together by a test on each side asserting the exact field names rather than by
|
||||||
// a shared type. If a field is renamed here and not there, that test fails on both sides.
|
// a shared type. If a field is renamed here and not there, that test fails on both sides.
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Version int `json:"v"`
|
Version int `json:"v"`
|
||||||
|
|
||||||
|
// Node is what the mesh calls this machine, and it arrives here because the node cannot work
|
||||||
|
// it out. The broker account it must authenticate as is named after it, so it has to be known
|
||||||
|
// before the mesh can say anything — and without it enrolment is a connection refused with an
|
||||||
|
// empty username, which names nothing.
|
||||||
|
Node string `json:"node,omitempty"`
|
||||||
|
|
||||||
Broker string `json:"broker,omitempty"`
|
Broker string `json:"broker,omitempty"`
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
Signer []byte `json:"signer,omitempty"`
|
Signer []byte `json:"signer,omitempty"`
|
||||||
|
|||||||
Reference in New Issue
Block a user