Merge pull request 'The provisioner client accepts credential form none through the mesh-sdk list (issue 774)' (#564) from fix/credential-forms-one-list into main
Reviewed-on: #564
This commit was merged in pull request #564.
This commit is contained in:
@@ -11,6 +11,8 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The provisioner's plan of a module on this node (novox/hq ADR 0349 clause 4, ADR 0570 clause 2): the seat
|
// The provisioner's plan of a module on this node (novox/hq ADR 0349 clause 4, ADR 0570 clause 2): the seat
|
||||||
@@ -90,7 +92,7 @@ type PlannedProvision struct {
|
|||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
ProviderModule string `json:"provider-module,omitempty"`
|
ProviderModule string `json:"provider-module,omitempty"`
|
||||||
// CredentialForm is the form the provider's offer declares for its credential (issue 768), which the vault acts
|
// CredentialForm is the form the provider's offer declares for its credential (issue 768), which the vault acts
|
||||||
// on and this client only reads: one of credentialForms, or none from a controller before it.
|
// on and this client only reads: one of the SDK's catalogue.CredentialForms, or empty from a controller before it.
|
||||||
CredentialForm string `json:"credential-form,omitempty"`
|
CredentialForm string `json:"credential-form,omitempty"`
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
// Contributes is what this module tells the provider about itself for this provision (a database's name, its
|
// Contributes is what this module tells the provider about itself for this provision (a database's name, its
|
||||||
@@ -110,12 +112,12 @@ type PlannedProvision struct {
|
|||||||
|
|
||||||
// issuedForHolder is whether the entry names a value its provider's software issued for this holder alone (issue
|
// issuedForHolder is whether the entry names a value its provider's software issued for this holder alone (issue
|
||||||
// 757). An entry of the login-per-consumer form that names a file (issue 779) is still the bind it was: its provider
|
// 757). An entry of the login-per-consumer form that names a file (issue 779) is still the bind it was: its provider
|
||||||
// lists this consumer and its bound values are read from it.
|
// lists this consumer and its bound values are read from it. So is an entry of an offer with no credential (form
|
||||||
func (p PlannedProvision) issuedForHolder() bool { return p.Name != "" && p.CredentialForm != "pair" }
|
// none, issue 774): the vault holds nothing for it, and its bound values are the provider's entry.
|
||||||
|
func (p PlannedProvision) issuedForHolder() bool {
|
||||||
// credentialForms are the forms a provider's offer may declare for its credential (mesh-vault pair.go): one value
|
return p.Name != "" && p.CredentialForm != catalogue.CredentialFormPair &&
|
||||||
// every holder shares, a login per consumer, a value its software issues, or one a person gives.
|
p.CredentialForm != catalogue.CredentialFormNone
|
||||||
var credentialForms = []string{"shared", "pair", "issued", "given"}
|
}
|
||||||
|
|
||||||
// PlannedSecret is one secret of the module's own, by its name in the module's manifest; Path, when set, is the
|
// PlannedSecret is one secret of the module's own, by its name in the module's manifest; Path, when set, is the
|
||||||
// file holding it alone (the manifest's `own-secrets`), which the client fills with its value and nothing else.
|
// file holding it alone (the manifest's `own-secrets`), which the client fills with its value and nothing else.
|
||||||
@@ -266,9 +268,10 @@ func (p Plan) problems() []string {
|
|||||||
if !nodeShape.MatchString(pr.Provider) {
|
if !nodeShape.MatchString(pr.Provider) {
|
||||||
out = append(out, fmt.Sprintf("%s: provider %q is not a machine's name", which, pr.Provider))
|
out = append(out, fmt.Sprintf("%s: provider %q is not a machine's name", which, pr.Provider))
|
||||||
}
|
}
|
||||||
if pr.CredentialForm != "" && !slices.Contains(credentialForms, pr.CredentialForm) {
|
// The SDK's one list of forms (issue 774): a copy kept here once lacked "none" and refused every such plan.
|
||||||
|
if pr.CredentialForm != "" && !catalogue.IsCredentialForm(pr.CredentialForm) {
|
||||||
out = append(out, fmt.Sprintf("%s: credential-form %q is not %s", which, pr.CredentialForm,
|
out = append(out, fmt.Sprintf("%s: credential-form %q is not %s", which, pr.CredentialForm,
|
||||||
strings.Join(credentialForms, ", ")))
|
strings.Join(catalogue.CredentialForms(), ", ")))
|
||||||
}
|
}
|
||||||
if key := [2]string{pr.Provision, pr.Local}; bound[key] {
|
if key := [2]string{pr.Provision, pr.Local}; bound[key] {
|
||||||
out = append(out, fmt.Sprintf("%s: its provider is named twice", which))
|
out = append(out, fmt.Sprintf("%s: its provider is named twice", which))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -63,7 +64,9 @@ func TestAPlanNamingEachProvidersCredentialFormIsRead(t *testing.T) {
|
|||||||
`{"provision":"sonarr-api","provider":"ace","provider-module":"sonarr","credential-form":"shared"},` +
|
`{"provision":"sonarr-api","provider":"ace","provider-module":"sonarr","credential-form":"shared"},` +
|
||||||
`{"name":"forge-read","provision":"forge-read","provider":"novox","provider-module":"forge",` +
|
`{"name":"forge-read","provision":"forge-read","provider":"novox","provider-module":"forge",` +
|
||||||
`"credential-form":"issued","path":"/var/lib/app/forge.secret"},` +
|
`"credential-form":"issued","path":"/var/lib/app/forge.secret"},` +
|
||||||
`{"provision":"smtp-relay","provider":"ace","provider-module":"relay","credential-form":"given"}]}}`
|
`{"provision":"smtp-relay","provider":"ace","provider-module":"relay","credential-form":"given"},` +
|
||||||
|
// An offer with no credential (issue 774), as design 47's catalogue declares gitea's forge-account.
|
||||||
|
`{"provision":"forge-account","provider":"novox","provider-module":"gitea","credential-form":"none"}]}}`
|
||||||
_, p, err := planOf("g14", "g14.app", []byte(value))
|
_, p, err := planOf("g14", "g14.app", []byte(value))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -72,9 +75,29 @@ func TestAPlanNamingEachProvidersCredentialFormIsRead(t *testing.T) {
|
|||||||
for _, pr := range p.Provisions {
|
for _, pr := range p.Provisions {
|
||||||
forms = append(forms, pr.CredentialForm)
|
forms = append(forms, pr.CredentialForm)
|
||||||
}
|
}
|
||||||
if strings.Join(forms, ",") != "pair,shared,issued,given" {
|
if strings.Join(forms, ",") != "pair,shared,issued,given,none" {
|
||||||
t.Fatalf("the credential forms are read as %v", forms)
|
t.Fatalf("the credential forms are read as %v", forms)
|
||||||
}
|
}
|
||||||
|
// A none entry places no file of its own and is never a holder's issued value: its bound values are read from
|
||||||
|
// its provider's entry, and the plan's files are placed as they are.
|
||||||
|
targets, fail := fillTargets(p, map[string]string{"app.conf": "x"})
|
||||||
|
var paths []string
|
||||||
|
for _, f := range targets {
|
||||||
|
paths = append(paths, f.Path)
|
||||||
|
}
|
||||||
|
if fail != "" || strings.Join(paths, ",") != "/etc/app.conf,/var/lib/app/forge.secret" {
|
||||||
|
t.Fatalf("the plan places %v (%s)", paths, fail)
|
||||||
|
}
|
||||||
|
if none := (PlannedProvision{Name: "forge-account", CredentialForm: "none"}); none.issuedForHolder() {
|
||||||
|
t.Fatal("a credential of an offer with none is read as a holder's issued value")
|
||||||
|
}
|
||||||
|
// A template naming such a credential fails loudly, and the vault, which mints nothing for it, is never asked.
|
||||||
|
named := Plan{Provisions: []PlannedProvision{{Name: "forge-account", Provision: "forge-account", Provider: "novox",
|
||||||
|
CredentialForm: "none"}}}
|
||||||
|
if _, wait, fail := (&Client{Node: "g14"}).secretOf(context.Background(), nil, "app", named,
|
||||||
|
Need{Kind: "secret", Name: "forge-account"}); wait != "" || !strings.Contains(fail, "no credential (form none)") {
|
||||||
|
t.Fatalf("a secret of an offer with none: wait %q, fail %q", wait, fail)
|
||||||
|
}
|
||||||
bad := strings.Replace(value, `"credential-form":"pair"`, `"credential-form":"one-for-all"`, 1)
|
bad := strings.Replace(value, `"credential-form":"pair"`, `"credential-form":"one-for-all"`, 1)
|
||||||
if _, _, err := planOf("g14", "g14.app", []byte(bad)); err == nil || !strings.Contains(err.Error(), "one-for-all") {
|
if _, _, err := planOf("g14", "g14.app", []byte(bad)); err == nil || !strings.Contains(err.Error(), "one-for-all") {
|
||||||
t.Fatalf("a credential form the client does not know was taken: %v", err)
|
t.Fatalf("a credential form the client does not know was taken: %v", err)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
)
|
)
|
||||||
@@ -171,6 +172,11 @@ func (c *Client) secretOf(ctx context.Context, s *state, module string, p Plan,
|
|||||||
} else if i := slices.IndexFunc(p.Provisions, func(pr PlannedProvision) bool { return pr.Name == n.Name }); i >= 0 {
|
} else if i := slices.IndexFunc(p.Provisions, func(pr PlannedProvision) bool { return pr.Name == n.Name }); i >= 0 {
|
||||||
// A credential of what the module requires, issued for this module on this machine (issue 757).
|
// A credential of what the module requires, issued for this module on this machine (issue 757).
|
||||||
pr := p.Provisions[i]
|
pr := p.Provisions[i]
|
||||||
|
if pr.CredentialForm == catalogue.CredentialFormNone {
|
||||||
|
// An offer with no credential (issue 774): the vault mints nothing, so asking it would wait for ever.
|
||||||
|
return "", "", fmt.Sprintf("%s: the offer of %s by %s has no credential (form none), so there is no "+
|
||||||
|
"secret %s to fill", n, pr.Provision, pr.Provider, n.Name)
|
||||||
|
}
|
||||||
name = HolderName(pr.Provider, pr.Provision, c.Node, module)
|
name = HolderName(pr.Provider, pr.Provision, c.Node, module)
|
||||||
if pr.CredentialForm == "pair" {
|
if pr.CredentialForm == "pair" {
|
||||||
// This consumer's own login from a provider that gives each consumer one (issue 779, ADR 0158 §11).
|
// This consumer's own login from a provider that gives each consumer one (issue 779, ADR 0158 §11).
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module mesh-provisioner-client
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030150314-9379e9a96ad4
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
golang.org/x/sys v0.48.0
|
golang.org/x/sys v0.48.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 h1:lKU3HjNXGiVbLW2hUgFjU/TMTUYMlNnnmZxG5T7biQA=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030150314-9379e9a96ad4 h1:CPUqC3FS/ezvBYdhVgVdsQg2DdN8xgtROQkSaHRKeOI=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030150314-9379e9a96ad4/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
Reference in New Issue
Block a user