Replace a value given by hand like one the mesh made (hq ADR 0228)
A given own secret the module reads at start is held by nobody but that module, so the mesh need not read it to replace it: secret rotate now works on it, and a value given through secret accept is replaced on its own after the module's first good start under the mesh. Only a value an outside party issues (own-secrets "issued-by": "outside") or one the module applies stays as given, refused with the reason.
This commit is contained in:
@@ -558,7 +558,10 @@ type Manifest struct {
|
||||
// that takes it only once, so a rotation must be staged beside the current value — the form the
|
||||
// mesh does not build yet, and refuses by name. A secret that says neither is not rotated by
|
||||
// the mesh: the one fault worse than an unrotated credential is a rotated one the software
|
||||
// never saw.
|
||||
// never saw. `"issued-by": "outside"` says a party outside the mesh issues the value — an API
|
||||
// key, a bot token, a licence — so the mesh never puts one of its own in its place (novox/hq
|
||||
// ADR 0228); any other at-start secret given by hand lives only until the module's first good
|
||||
// start, and is then replaced.
|
||||
OwnSecrets OwnSecrets `json:"own-secrets,omitempty"`
|
||||
|
||||
// SecretsOwner is who the files holding this module's secrets belong to on the machine —
|
||||
@@ -1801,6 +1804,13 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
||||
"or %q (applied by the module's own code to a backend that takes it once)",
|
||||
m.Module, name, own.Taken, TakenAtStart, TakenApplied))
|
||||
}
|
||||
if own.IssuedBy != "" && own.IssuedBy != IssuedOutside {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s says its secret %q is issued by %q; a secret says %q when a party outside the mesh "+
|
||||
"issues it — a vendor's key, a bot's token, a licence — and says nothing when the mesh "+
|
||||
"may make it (novox/hq ADR 0228)",
|
||||
m.Module, name, own.IssuedBy, IssuedOutside))
|
||||
}
|
||||
}
|
||||
localOf := map[string]string{}
|
||||
for _, to := range m.SecretRequirements() {
|
||||
@@ -2263,10 +2273,24 @@ const (
|
||||
TakenApplied = "applied"
|
||||
)
|
||||
|
||||
// OwnSecret is where one of a module's own secrets lands, and how the module takes it.
|
||||
// IssuedOutside says a value was issued by a party outside the mesh — a vendor's API key, a bot's
|
||||
// token, a licence — so no value the mesh makes would work in its place (novox/hq ADR 0228).
|
||||
const IssuedOutside = "outside"
|
||||
|
||||
// OwnSecret is where one of a module's own secrets lands, how the module takes it, and — for a
|
||||
// value only an outside party can issue — that it is one.
|
||||
type OwnSecret struct {
|
||||
Path string
|
||||
Taken string
|
||||
Path string
|
||||
Taken string
|
||||
IssuedBy string
|
||||
}
|
||||
|
||||
// MeshMayMake says the mesh may put a value it makes in place of the one the secret holds: the
|
||||
// module reads it as it starts, and nobody outside the mesh issued it (novox/hq ADR 0228). A value
|
||||
// given to the mesh for such a secret lives only until the module's first good start under the
|
||||
// mesh; anything else given stays as given.
|
||||
func (s OwnSecret) MeshMayMake() bool {
|
||||
return s.Taken == TakenAtStart && s.IssuedBy != IssuedOutside
|
||||
}
|
||||
|
||||
// OwnSecrets is a module's own secrets by name. On the wire each is a path, or an object naming
|
||||
@@ -2287,15 +2311,16 @@ func (o *OwnSecrets) UnmarshalJSON(raw []byte) error {
|
||||
continue
|
||||
}
|
||||
var long struct {
|
||||
Path string `json:"path"`
|
||||
Taken string `json:"taken,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Taken string `json:"taken,omitempty"`
|
||||
IssuedBy string `json:"issued-by,omitempty"`
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewReader(body))
|
||||
dec.DisallowUnknownFields()
|
||||
if err := dec.Decode(&long); err != nil {
|
||||
return fmt.Errorf("own-secrets.%s: a path, or {\"path\", \"taken\"}: %w", name, err)
|
||||
return fmt.Errorf("own-secrets.%s: a path, or {\"path\", \"taken\", \"issued-by\"}: %w", name, err)
|
||||
}
|
||||
out[name] = OwnSecret{Path: long.Path, Taken: long.Taken}
|
||||
out[name] = OwnSecret{Path: long.Path, Taken: long.Taken, IssuedBy: long.IssuedBy}
|
||||
}
|
||||
*o = out
|
||||
return nil
|
||||
@@ -2304,11 +2329,18 @@ func (o *OwnSecrets) UnmarshalJSON(raw []byte) error {
|
||||
func (o OwnSecrets) MarshalJSON() ([]byte, error) {
|
||||
entries := make(map[string]any, len(o))
|
||||
for name, s := range o {
|
||||
if s.Taken == "" {
|
||||
if s.Taken == "" && s.IssuedBy == "" {
|
||||
entries[name] = s.Path
|
||||
continue
|
||||
}
|
||||
entries[name] = map[string]string{"path": s.Path, "taken": s.Taken}
|
||||
long := map[string]string{"path": s.Path}
|
||||
if s.Taken != "" {
|
||||
long["taken"] = s.Taken
|
||||
}
|
||||
if s.IssuedBy != "" {
|
||||
long["issued-by"] = s.IssuedBy
|
||||
}
|
||||
entries[name] = long
|
||||
}
|
||||
return json.Marshal(entries)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user