The key keeper is ssh-client's published package sshkeys, taken by series by the three build modules; Said, KindBuilder, ExecRunnerClean from the Delivery sdk (B5d; ADR 0634 §1; issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -71,7 +72,7 @@ func FetchCommit(ctx context.Context, run forgekey.Runner, dir, remote, commit,
|
||||
full := append([]string{"-c", "credential.helper=", "-C", dir}, args...)
|
||||
r := run(ctx, env, "git", full...)
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
return "", fmt.Errorf("git %s: %s", args[0], forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return "", fmt.Errorf("git %s: %s", args[0], dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
@@ -132,7 +133,7 @@ func UpdateMirror(ctx context.Context, run forgekey.Runner, mirror, remote, comm
|
||||
if verb == "-C" && len(args) > 2 { // the mirror's commands are named by what they do, not by -C
|
||||
verb = args[2]
|
||||
}
|
||||
return fmt.Errorf("git %s: %s", verb, forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return fmt.Errorf("git %s: %s", verb, dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -55,7 +56,7 @@ func MergeInto(ctx context.Context, run forgekey.Runner, dir, remote, base, comm
|
||||
full := append([]string{"-c", "credential.helper=", "-C", dir}, args...)
|
||||
r := run(ctx, env, "git", full...)
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
return "", fmt.Errorf("git %s: %s", args[0], forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return "", fmt.Errorf("git %s: %s", args[0], dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
@@ -26,7 +27,7 @@ var lockFiles = []string{"go.sum", "package-lock.json"}
|
||||
func ModuleTrees(ctx context.Context, run forgekey.Runner, env []string, dir, commit string) (map[string]sdkstep.ModuleTree, error) {
|
||||
r := run(ctx, env, "git", "-C", dir, "ls-files", "--", "module.json", "*/module.json")
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
return nil, fmt.Errorf("git ls-files: %s", forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return nil, fmt.Errorf("git ls-files: %s", dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
trees := map[string]sdkstep.ModuleTree{}
|
||||
for _, l := range strings.Split(r.Stdout, "\n") {
|
||||
@@ -40,7 +41,7 @@ func ModuleTrees(ctx context.Context, run forgekey.Runner, env []string, dir, co
|
||||
}
|
||||
t := run(ctx, env, "git", "-C", dir, "rev-parse", spec)
|
||||
if t.Status != 0 || t.Err != "" {
|
||||
return nil, fmt.Errorf("git rev-parse %s: %s", spec, forgekey.Said(t.Err+"\n"+t.Stderr))
|
||||
return nil, fmt.Errorf("git rev-parse %s: %s", spec, dforgekey.Said(t.Err+"\n"+t.Stderr))
|
||||
}
|
||||
mt := sdkstep.ModuleTree{Tree: strings.TrimSpace(t.Stdout)}
|
||||
raw, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(l)))
|
||||
@@ -86,7 +87,7 @@ func narrow(ctx context.Context, run forgekey.Runner, env []string, dir, commit,
|
||||
for _, round := range unnested(full) {
|
||||
r := run(ctx, env, "git", append([]string{"-C", dir, "ls-tree", "-z", commit, "--"}, round...)...)
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
return fmt.Errorf("git ls-tree %s of the module %s: %s", commit, mdir, forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return fmt.Errorf("git ls-tree %s of the module %s: %s", commit, mdir, dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
for _, l := range strings.Split(r.Stdout, "\x00") {
|
||||
meta, name, ok := strings.Cut(l, "\t")
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
)
|
||||
|
||||
// The runners of a digest or test step: this installed program, or the candidate's compiled from the workspace.
|
||||
@@ -111,7 +111,7 @@ func (m *Module) mergeCommit(ctx context.Context, workspace string) (string, err
|
||||
r := m.Run(ctx, nil, "git", "-C", filepath.Join(m.Work, workspace, "tree"), "rev-parse", "HEAD")
|
||||
commit := strings.TrimSpace(r.Stdout)
|
||||
if r.Status != 0 || r.Err != "" || commit == "" {
|
||||
return "", fmt.Errorf("the workspace's commit: git rev-parse: %s", forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return "", fmt.Errorf("the workspace's commit: git rev-parse: %s", dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
)
|
||||
|
||||
var commitOK = regexp.MustCompile(`^[0-9a-f]{40}$`)
|
||||
@@ -274,7 +274,7 @@ func (m *Module) downloadAndVerify(ctx context.Context, dir, d string) error {
|
||||
func (m *Module) goModDirs(ctx context.Context, tree string) ([]string, error) {
|
||||
r := m.Run(ctx, nil, "git", "-C", tree, "ls-files", "--", "go.mod", "*/go.mod")
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
return nil, fmt.Errorf("git ls-files: %s", forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return nil, fmt.Errorf("git ls-files: %s", dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
dirs := []string{}
|
||||
for _, l := range strings.Split(r.Stdout, "\n") {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -237,7 +238,7 @@ func TestTheKeeperWritesTheModulesEntryWithKindBuilder(t *testing.T) {
|
||||
if !ok || len(st.puts) != 1 {
|
||||
t.Fatalf("entries = %v, want one at ace.%s", st.puts, forgeModule)
|
||||
}
|
||||
var e forgekey.KeyEntry
|
||||
var e sshkeys.KeyEntry
|
||||
if err := json.Unmarshal(raw, &e); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -64,7 +65,7 @@ func (m *Module) stepRunner(workspace string, write bool) (forgekey.Runner, erro
|
||||
func ProbeBubblewrap(ctx context.Context) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, ProbeTimeout)
|
||||
defer cancel()
|
||||
r := forgekey.ExecRunnerClean(0)(ctx, []string{"PATH=" + defaultPath}, buildstep.Bubblewrap, "--version")
|
||||
r := dforgekey.ExecRunnerClean(0)(ctx, []string{"PATH=" + defaultPath}, buildstep.Bubblewrap, "--version")
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
return "", fmt.Errorf("`%s --version` failed: %s", buildstep.Bubblewrap, firstLine(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
|
||||
@@ -13,14 +13,16 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
// keeperFor is this module's keeper: go/forgekey's, of kind builder, for account in home on node, whose entry lies
|
||||
// at <node>.<account> of the keys state.
|
||||
func keeperFor(home, node, account string, run forgekey.Runner, state forgekey.KeyPutter) *forgekey.KeyKeeper {
|
||||
return &forgekey.KeyKeeper{Home: home, Node: node, Account: account, Kind: forgekey.KindBuilder, Run: run, State: state, Now: time.Now}
|
||||
func keeperFor(home, node, account string, run forgekey.Runner, state sshkeys.KeyPutter) *sshkeys.KeyKeeper {
|
||||
return &sshkeys.KeyKeeper{Home: home, Node: node, Account: account, Kind: dforgekey.KindBuilder, Run: run, State: state, Now: time.Now}
|
||||
}
|
||||
|
||||
// startKeeper runs the keeper when the runtime launched this process for a node: outside a launch — a build, a
|
||||
|
||||
@@ -51,6 +51,10 @@ func resource(t *testing.T, m catalogue.Manifest, id string) map[string]any {
|
||||
// packages.
|
||||
func TestManifestDeclaresTheAccountTheCapabilityAndThePackages(t *testing.T) {
|
||||
m := readManifest(t)
|
||||
// The key keeper is ssh-client's published package, taken by its series (ADR 0634 §1).
|
||||
if m.Packages["sshkeys"] != "0.1" {
|
||||
t.Errorf("packages %v: want sshkeys of series 0.1", m.Packages)
|
||||
}
|
||||
if m.Module != "mesh-build-go" || m.RunsAs != "mesh-build-go" {
|
||||
t.Errorf("module %q runs as %q, want mesh-build-go as itself", m.Module, m.RunsAs)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
)
|
||||
|
||||
// mirrorLocks hold one mutex per mirror of this process; the lock file beside the mirror holds off another process.
|
||||
@@ -68,7 +68,7 @@ func (m *Module) dissociate(ctx context.Context, dir string) error {
|
||||
return nil // no mirror was used
|
||||
}
|
||||
if r := m.Run(ctx, []string{"GIT_TERMINAL_PROMPT=0"}, "git", "-C", dir, "repack", "-a", "-d", "-q"); r.Status != 0 || r.Err != "" {
|
||||
return fmt.Errorf("git repack in %s: %s", dir, forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return fmt.Errorf("git repack in %s: %s", dir, dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
return os.Remove(alternates)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
)
|
||||
|
||||
const forgePrefix = "git.novox.be/novox/"
|
||||
@@ -140,7 +140,7 @@ func (m *Module) placePin(ctx context.Context, root string, p pin, failure strin
|
||||
r := m.Run(ctx, []string{"GIT_TERMINAL_PROMPT=0"}, "git", "-c", "credential.helper=", "ls-remote", m.remote(repository), "refs/pull/*/head")
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
// The forge did not answer: retaken, with its own cause, never held as a refusal until the module changes (issue 1748).
|
||||
return "", fmt.Errorf("the pin of %s at commit %s: listing the pull requests of %s failed (a download failure that may be retaken): %s", p.module, p.short, repository, forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return "", fmt.Errorf("the pin of %s at commit %s: listing the pull requests of %s failed (a download failure that may be retaken): %s", p.module, p.short, repository, dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
commit, ref := "", ""
|
||||
for _, l := range strings.Split(r.Stdout, "\n") {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -18,7 +19,7 @@ const ProbeTimeout = 15 * time.Second
|
||||
// module builds with, and the third word of its answer is that version. It downloads nothing and needs no network.
|
||||
// The environment is built from scratch like a step's (StepRunner), so the module's bus credentials are not in it.
|
||||
func ProbeGo(home, cache string) func(ctx context.Context) (string, error) {
|
||||
run := forgekey.ExecRunnerClean(0)
|
||||
run := dforgekey.ExecRunnerClean(0)
|
||||
return func(ctx context.Context) (string, error) { return probeGo(ctx, run, home, cache) }
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
// that base given; a command is bound by its context alone (the bound 0), so a fetch may outlast a tool call's thirty
|
||||
// seconds (the step has its own context).
|
||||
func StepRunner(home string) forgekey.Runner {
|
||||
run := forgekey.ExecRunnerClean(0)
|
||||
run := dforgekey.ExecRunnerClean(0)
|
||||
return func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||
return run(ctx, append(baseEnv(home), env...), name, args...)
|
||||
}
|
||||
|
||||
@@ -97,3 +97,7 @@ require (
|
||||
)
|
||||
|
||||
replace git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep => ./buildstep
|
||||
|
||||
require git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys v0.0.0-00010101000000-000000000000
|
||||
|
||||
replace git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys => ../ssh-client/sshkeys
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"module": "mesh-build-go",
|
||||
"version": "1",
|
||||
"packages": {
|
||||
"sshkeys": "0.1"
|
||||
},
|
||||
"moved": true,
|
||||
"runs-as": "mesh-build-go",
|
||||
"capabilities": [
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -235,7 +236,7 @@ func TestTheKeeperWritesTheModulesEntryWithKindBuilder(t *testing.T) {
|
||||
if !ok || len(st.puts) != 1 {
|
||||
t.Fatalf("entries = %v, want one at ace.%s", st.puts, forgeModule)
|
||||
}
|
||||
var e forgekey.KeyEntry
|
||||
var e sshkeys.KeyEntry
|
||||
if err := json.Unmarshal(raw, &e); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -13,14 +13,16 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
// keeperFor is this module's keeper: go/forgekey's, of kind builder, for account in home on node, whose entry lies
|
||||
// at <node>.<account> of the keys state.
|
||||
func keeperFor(home, node, account string, run forgekey.Runner, state forgekey.KeyPutter) *forgekey.KeyKeeper {
|
||||
return &forgekey.KeyKeeper{Home: home, Node: node, Account: account, Kind: forgekey.KindBuilder, Run: run, State: state, Now: time.Now}
|
||||
func keeperFor(home, node, account string, run forgekey.Runner, state sshkeys.KeyPutter) *sshkeys.KeyKeeper {
|
||||
return &sshkeys.KeyKeeper{Home: home, Node: node, Account: account, Kind: dforgekey.KindBuilder, Run: run, State: state, Now: time.Now}
|
||||
}
|
||||
|
||||
// startKeeper runs the keeper when the runtime launched this process for a node: outside a launch — a build, a
|
||||
|
||||
@@ -53,6 +53,10 @@ func resource(t *testing.T, m catalogue.Manifest, id string) map[string]any {
|
||||
// openssh; buildah is not its package (the buildah module's alone).
|
||||
func TestManifestDeclaresTheAccountTheCapabilityAndThePackages(t *testing.T) {
|
||||
m := readManifest(t)
|
||||
// The key keeper is ssh-client's published package, taken by its series (ADR 0634 §1).
|
||||
if m.Packages["sshkeys"] != "0.1" {
|
||||
t.Errorf("packages %v: want sshkeys of series 0.1", m.Packages)
|
||||
}
|
||||
if m.Module != "mesh-build-image" || m.RunsAs != "mesh-build-image" {
|
||||
t.Errorf("module %q runs as %q, want mesh-build-image as itself", m.Module, m.RunsAs)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
// that base given; a command is bound by its context alone (the bound 0), so a fetch may outlast a tool call's thirty
|
||||
// seconds (the step has its own context).
|
||||
func StepRunner(home string) forgekey.Runner {
|
||||
run := forgekey.ExecRunnerClean(0)
|
||||
run := dforgekey.ExecRunnerClean(0)
|
||||
return func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||
return run(ctx, append(baseEnv(home), env...), name, args...)
|
||||
}
|
||||
|
||||
@@ -34,3 +34,7 @@ require (
|
||||
)
|
||||
|
||||
replace git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep => ../mesh-build-go/buildstep
|
||||
|
||||
require git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys v0.0.0-00010101000000-000000000000
|
||||
|
||||
replace git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys => ../ssh-client/sshkeys
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"module": "mesh-build-image",
|
||||
"version": "1",
|
||||
"packages": {
|
||||
"sshkeys": "0.1"
|
||||
},
|
||||
"moved": true,
|
||||
"runs-as": "mesh-build-image",
|
||||
"capabilities": [
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -215,7 +216,7 @@ func (m *Module) fetchInto(ctx context.Context, req sdkstep.FetchRequest, group
|
||||
func (m *Module) typeScriptModules(ctx context.Context, tree string) (dirs []string, packages map[string]bool, err error) {
|
||||
r := m.Run(ctx, m.baseEnv(), "git", "-C", tree, "ls-files", "--", "module.json", "*/module.json")
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
return nil, nil, fmt.Errorf("git ls-files: %s", forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
return nil, nil, fmt.Errorf("git ls-files: %s", dforgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
packages = map[string]bool{}
|
||||
for _, l := range strings.Split(r.Stdout, "\n") {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -269,7 +270,7 @@ func TestNoCommandOfTheStepSeesATokenOfTheProcess(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// The runner main wires: what it hands a command is exactly what the step gave.
|
||||
ran := forgekey.ExecRunnerClean(10*time.Second)(context.Background(), append(m.baseEnv(), "ONLY=this"), "env")
|
||||
ran := dforgekey.ExecRunnerClean(10*time.Second)(context.Background(), append(m.baseEnv(), "ONLY=this"), "env")
|
||||
if ran.Status != 0 || strings.Contains(ran.Stdout, "hunter") || !strings.Contains(ran.Stdout, "ONLY=this") {
|
||||
t.Errorf("the clean runner printed %q", ran.Stdout)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -235,7 +236,7 @@ func TestTheKeeperWritesTheModulesEntryWithKindBuilder(t *testing.T) {
|
||||
if !ok || len(st.puts) != 1 {
|
||||
t.Fatalf("entries = %v, want one at ace.%s", st.puts, forgeModule)
|
||||
}
|
||||
var e forgekey.KeyEntry
|
||||
var e sshkeys.KeyEntry
|
||||
if err := json.Unmarshal(raw, &e); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -53,7 +54,7 @@ func (m *Module) stepRunner(root string, write bool) (forgekey.Runner, error) {
|
||||
func ProbeBubblewrap(ctx context.Context) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||
defer cancel()
|
||||
r := forgekey.ExecRunnerClean(0)(ctx, []string{"PATH=" + defaultPath}, buildstep.Bubblewrap, "--version")
|
||||
r := dforgekey.ExecRunnerClean(0)(ctx, []string{"PATH=" + defaultPath}, buildstep.Bubblewrap, "--version")
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
return "", fmt.Errorf("`%s --version` failed: %s", buildstep.Bubblewrap, firstLine(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
|
||||
@@ -13,14 +13,16 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
// keeperFor is this module's keeper: go/forgekey's, of kind builder, for account in home on node, whose entry lies
|
||||
// at <node>.<account> of the keys state.
|
||||
func keeperFor(home, node, account string, run forgekey.Runner, state forgekey.KeyPutter) *forgekey.KeyKeeper {
|
||||
return &forgekey.KeyKeeper{Home: home, Node: node, Account: account, Kind: forgekey.KindBuilder, Run: run, State: state, Now: time.Now}
|
||||
func keeperFor(home, node, account string, run forgekey.Runner, state sshkeys.KeyPutter) *sshkeys.KeyKeeper {
|
||||
return &sshkeys.KeyKeeper{Home: home, Node: node, Account: account, Kind: dforgekey.KindBuilder, Run: run, State: state, Now: time.Now}
|
||||
}
|
||||
|
||||
// startKeeper runs the keeper when the runtime launched this process for a node: outside a launch — a build, a
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
// CacheDir is the cache directory, outside the account's home (ADR 0466 §1; module.json resource `cache`).
|
||||
@@ -42,7 +42,7 @@ func main() {
|
||||
Node: strings.TrimSpace(os.Getenv("MESH_NODE")),
|
||||
Reload: func() (Recipe, error) { return ReadRecipe(paths.LockPath()) },
|
||||
Probe: ProbeNode,
|
||||
Toolchain: &Toolchain{Paths: paths, Run: forgekey.ExecRunnerClean(0), Registry: registry},
|
||||
Toolchain: &Toolchain{Paths: paths, Run: dforgekey.ExecRunnerClean(0), Registry: registry},
|
||||
Toolchains: stdio.State(delivery.ToolchainsState).Put,
|
||||
Emit: func(t string, b any) error { return stdio.Emit(t, b) },
|
||||
Steps: stdio.State(delivery.StepsState).Put,
|
||||
@@ -51,7 +51,7 @@ func main() {
|
||||
ReadPauses: readOf(stdio.State(delivery.PausesState)),
|
||||
ReadToolchainsState: readOf(stdio.State(delivery.ToolchainsState)),
|
||||
Work: WorkDir,
|
||||
Run: forgekey.ExecRunnerClean(0),
|
||||
Run: dforgekey.ExecRunnerClean(0),
|
||||
Ctx: ctx,
|
||||
Say: logf,
|
||||
Bubblewrap: ProbeBubblewrap,
|
||||
|
||||
@@ -46,6 +46,10 @@ func resource(t *testing.T, m catalogue.Manifest, id string) map[string]any {
|
||||
// home at mode 0700, requires user-namespaces and declares the four packages and carries toolchain/package-lock.json.
|
||||
func TestManifestDeclaresTheAccountTheCapabilityAndThePackages(t *testing.T) {
|
||||
m := readManifest(t)
|
||||
// The key keeper is ssh-client's published package, taken by its series (ADR 0634 §1).
|
||||
if m.Packages["sshkeys"] != "0.1" {
|
||||
t.Errorf("packages %v: want sshkeys of series 0.1", m.Packages)
|
||||
}
|
||||
if m.Module != "mesh-build-node" || m.RunsAs != "mesh-build-node" {
|
||||
t.Errorf("module %q runs as %q, want mesh-build-node as itself", m.Module, m.RunsAs)
|
||||
}
|
||||
|
||||
@@ -94,3 +94,7 @@ require (
|
||||
)
|
||||
|
||||
replace git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep => ../mesh-build-go/buildstep
|
||||
|
||||
require git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys v0.0.0-00010101000000-000000000000
|
||||
|
||||
replace git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys => ../ssh-client/sshkeys
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"module": "mesh-build-node",
|
||||
"version": "1",
|
||||
"packages": {
|
||||
"sshkeys": "0.1"
|
||||
},
|
||||
"moved": true,
|
||||
"runs-as": "mesh-build-node",
|
||||
"capabilities": [
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -209,7 +210,7 @@ func (c *Client) Keys(ctx context.Context) ([]Key, error) {
|
||||
k.AgeDays = int(c.Now().Sub(info.ModTime()).Hours() / 24)
|
||||
}
|
||||
if pub, err := os.ReadFile(path + ".pub"); err == nil {
|
||||
if pk, err := forgekey.ParseKeyLine(string(pub)); err == nil {
|
||||
if pk, err := sshkeys.ParseKeyLine(string(pub)); err == nil {
|
||||
k.Type, k.Bits, k.Fingerprint, k.Comment, k.Weak = pk.Type, pk.Bits, pk.Fingerprint, pk.Comment, pk.Weak
|
||||
}
|
||||
} else {
|
||||
@@ -227,7 +228,7 @@ func (c *Client) Keys(ctx context.Context) ([]Key, error) {
|
||||
var derived string
|
||||
k.Passphrase, derived = c.passphrase(ctx, path)
|
||||
if derived != "" {
|
||||
if pk, err := forgekey.ParseKeyLine(derived); err == nil {
|
||||
if pk, err := sshkeys.ParseKeyLine(derived); err == nil {
|
||||
if k.Fingerprint != "" && !k.PublicMissing && pk.Fingerprint != k.Fingerprint {
|
||||
k.PublicMismatch = fmt.Sprintf("the key is %s; its .pub is %s", pk.Fingerprint, k.Fingerprint)
|
||||
}
|
||||
@@ -266,7 +267,7 @@ func (c *Client) passphrase(ctx context.Context, path string) (string, string) {
|
||||
|
||||
// AuthorizedKey is one line of authorized_keys, by fingerprint.
|
||||
type AuthorizedKey struct {
|
||||
forgekey.PublicKey
|
||||
sshkeys.PublicKey
|
||||
Line int `json:"line"`
|
||||
InMesh bool `json:"in_mesh_region,omitempty"`
|
||||
}
|
||||
@@ -311,7 +312,7 @@ func (c *Client) readAuthorized() ([]AuthorizedKey, []int, error) {
|
||||
case t == "" || strings.HasPrefix(t, "#"):
|
||||
continue
|
||||
}
|
||||
k, err := forgekey.ParseKeyLine(t)
|
||||
k, err := sshkeys.ParseKeyLine(t)
|
||||
if err != nil {
|
||||
bad = append(bad, n+1)
|
||||
continue
|
||||
@@ -399,26 +400,26 @@ func atomicWrite(path string, data []byte, mode fs.FileMode) error {
|
||||
// ---- known_hosts -------------------------------------------------------------------------------
|
||||
|
||||
// knownFor is what known_hosts holds for one host (port 22, or [host]:port), by fingerprint.
|
||||
func (c *Client) knownFor(ctx context.Context, host string, port int) ([]forgekey.PublicKey, error) {
|
||||
func (c *Client) knownFor(ctx context.Context, host string, port int) ([]sshkeys.PublicKey, error) {
|
||||
name := host
|
||||
if port != 22 {
|
||||
name = fmt.Sprintf("[%s]:%d", host, port)
|
||||
}
|
||||
file := c.ssh("known_hosts")
|
||||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||
return []forgekey.PublicKey{}, nil
|
||||
return []sshkeys.PublicKey{}, nil
|
||||
}
|
||||
r := c.Run(ctx, nil, "ssh-keygen", "-F", name, "-f", file)
|
||||
if r.Err != "" {
|
||||
return nil, named("ssh-keygen -F", r)
|
||||
}
|
||||
// Exit 1 with nothing printed is "not found".
|
||||
keys := []forgekey.PublicKey{}
|
||||
keys := []sshkeys.PublicKey{}
|
||||
for _, l := range strings.Split(r.Stdout, "\n") {
|
||||
if l = strings.TrimSpace(l); l == "" || strings.HasPrefix(l, "#") {
|
||||
continue
|
||||
}
|
||||
if k, err := forgekey.ParseKeyLine(l); err == nil {
|
||||
if k, err := sshkeys.ParseKeyLine(l); err == nil {
|
||||
k.Comment, k.Options = "", ""
|
||||
keys = append(keys, k)
|
||||
}
|
||||
@@ -427,17 +428,17 @@ func (c *Client) knownFor(ctx context.Context, host string, port int) ([]forgeke
|
||||
}
|
||||
|
||||
// scan asks a host for its keys now.
|
||||
func (c *Client) scan(ctx context.Context, host string, port int) ([]forgekey.PublicKey, []string, error) {
|
||||
func (c *Client) scan(ctx context.Context, host string, port int) ([]sshkeys.PublicKey, []string, error) {
|
||||
r := c.Run(ctx, nil, "ssh-keyscan", "-T", "5", "-p", fmt.Sprint(port), host)
|
||||
if r.Err != "" {
|
||||
return nil, nil, named("ssh-keyscan", r)
|
||||
}
|
||||
keys, lines := []forgekey.PublicKey{}, []string{}
|
||||
keys, lines := []sshkeys.PublicKey{}, []string{}
|
||||
for _, l := range strings.Split(r.Stdout, "\n") {
|
||||
if l = strings.TrimSpace(l); l == "" || strings.HasPrefix(l, "#") {
|
||||
continue
|
||||
}
|
||||
if k, err := forgekey.ParseKeyLine(l); err == nil {
|
||||
if k, err := sshkeys.ParseKeyLine(l); err == nil {
|
||||
k.Comment, k.Options = "", ""
|
||||
keys = append(keys, k)
|
||||
lines = append(lines, l)
|
||||
@@ -457,7 +458,7 @@ func orElse(s, def string) string {
|
||||
}
|
||||
|
||||
// compare says how what is known stands against what the host offers now.
|
||||
func compare(known, live []forgekey.PublicKey) string {
|
||||
func compare(known, live []sshkeys.PublicKey) string {
|
||||
if len(known) == 0 {
|
||||
return "not known: the first connection would ask"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -60,7 +61,7 @@ func TestTheBundlesKeeperIsForgekeysOfKindMachineWithItsEntryAtNodeAccount(t *te
|
||||
if !ok || len(st.puts) != 1 {
|
||||
t.Fatalf("entries = %v, want one at g14.nox", st.puts)
|
||||
}
|
||||
var e forgekey.KeyEntry
|
||||
var e sshkeys.KeyEntry
|
||||
if err := json.Unmarshal(raw, &e); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
"github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
@@ -171,7 +171,7 @@ func TestTheKeyProcessMakesTheKeyAndPutsItsEntryOverTheBus(t *testing.T) {
|
||||
if raw == nil {
|
||||
t.Fatalf("no entry %s in the keys state within 60 s", key)
|
||||
}
|
||||
var e forgekey.KeyEntry
|
||||
var e sshkeys.KeyEntry
|
||||
if err := json.Unmarshal(raw, &e); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -181,7 +181,7 @@ func TestTheKeyProcessMakesTheKeyAndPutsItsEntryOverTheBus(t *testing.T) {
|
||||
if strings.Contains(strings.ToUpper(string(raw)), "PRIVATE") {
|
||||
t.Errorf("the entry carries private material: %s", raw)
|
||||
}
|
||||
info, err := os.Stat(filepath.Join(home, ".ssh", forgekey.KeyFile))
|
||||
info, err := os.Stat(filepath.Join(home, ".ssh", sshkeys.KeyFile))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
@@ -158,16 +159,16 @@ func startKeeper() {
|
||||
|
||||
// keeperFor is the machine's account's keeper: go/forgekey's, of kind machine (ADR 0400 §1, ADR 0466 §2), whose key
|
||||
// file is ~/.ssh/mesh_ed25519 and whose entry lies at <node>.<account> in the keys state.
|
||||
func keeperFor(home, node, account string, run forgekey.Runner, state forgekey.KeyPutter) *forgekey.KeyKeeper {
|
||||
return &forgekey.KeyKeeper{Home: home, Node: node, Account: account, Kind: forgekey.KindMachine, Run: run, State: state, Now: time.Now}
|
||||
func keeperFor(home, node, account string, run forgekey.Runner, state sshkeys.KeyPutter) *sshkeys.KeyKeeper {
|
||||
return &sshkeys.KeyKeeper{Home: home, Node: node, Account: account, Kind: sshkeys.KindMachine, Run: run, State: state, Now: time.Now}
|
||||
}
|
||||
|
||||
// keepSwept sweeps ~/.ssh (sweep.go) now and every forgekey.KeyRound.
|
||||
// keepSwept sweeps ~/.ssh (sweep.go) now and every sshkeys.KeyRound.
|
||||
func keepSwept(c *Client) {
|
||||
say := func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[ssh-client] "+f+"\n", a...) }
|
||||
for {
|
||||
c.SweepOnce(time.Now(), say)
|
||||
time.Sleep(forgekey.KeyRound)
|
||||
time.Sleep(sshkeys.KeyRound)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -183,15 +184,15 @@ func TestAPublicHalfThatIsAnotherKeysIsFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFingerprintsAreSshKeygens(t *testing.T) {
|
||||
k, err := forgekey.ParseKeyLine("from=\"10.0.0.0/8,192.0.2.1\",no-pty " + edPub)
|
||||
k, err := sshkeys.ParseKeyLine("from=\"10.0.0.0/8,192.0.2.1\",no-pty " + edPub)
|
||||
if err != nil || k.Fingerprint != edFP || k.Bits != 256 || k.Comment != "op@laptop" || !strings.HasPrefix(k.Options, "from=") {
|
||||
t.Fatalf("%+v %v", k, err)
|
||||
}
|
||||
k, _ = forgekey.ParseKeyLine(rsaPub)
|
||||
k, _ = sshkeys.ParseKeyLine(rsaPub)
|
||||
if k.Fingerprint != rsaFP || k.Bits != 2048 || !strings.Contains(k.Weak, "below 3072") {
|
||||
t.Fatalf("%+v", k)
|
||||
}
|
||||
if _, err := forgekey.ParseKeyLine("ssh-ed25519 !!!notbase64"); err == nil {
|
||||
if _, err := sshkeys.ParseKeyLine("ssh-ed25519 !!!notbase64"); err == nil {
|
||||
t.Fatal("garbage accepted")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,20 +22,20 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
)
|
||||
|
||||
// Declared are the paths under ~/.ssh this module declares (module.json) or its own process makes
|
||||
// (keykeeper.go). A test checks the manifest against this list.
|
||||
var Declared = map[string]bool{
|
||||
"config": true,
|
||||
"known_hosts": true,
|
||||
"config.d": true,
|
||||
"config.d/00-mesh": true,
|
||||
"config.d/10-forge": true,
|
||||
forgekey.KeyFile: true,
|
||||
forgekey.KeyFile + ".pub": true,
|
||||
"authorized_keys": true,
|
||||
"config": true,
|
||||
"known_hosts": true,
|
||||
"config.d": true,
|
||||
"config.d/00-mesh": true,
|
||||
"config.d/10-forge": true,
|
||||
sshkeys.KeyFile: true,
|
||||
sshkeys.KeyFile + ".pub": true,
|
||||
"authorized_keys": true,
|
||||
}
|
||||
|
||||
// SSHReads are the files ssh and sshd read in ~/.ssh by themselves, though this module writes none of
|
||||
@@ -373,7 +373,7 @@ func publicKey(path string) bool {
|
||||
return false
|
||||
}
|
||||
line, _, _ := strings.Cut(string(raw), "\n")
|
||||
if _, err := forgekey.ParseKeyLine(line); err == nil {
|
||||
if _, err := sshkeys.ParseKeyLine(line); err == nil {
|
||||
return true
|
||||
}
|
||||
f := strings.Fields(line)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
"git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys"
|
||||
)
|
||||
|
||||
// ADR 0415, issue 1017: the sweep keeps ~/.ssh to what this module knows and moves the rest out.
|
||||
@@ -75,7 +75,7 @@ func TestSweepKeepsWhatItKnows(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, rel := range []string{"config", "known_hosts", "authorized_keys", "config.d/00-mesh", "config.d/10-forge",
|
||||
forgekey.KeyFile, forgekey.KeyFile + ".pub", "id_ed25519", "id_ed25519.pub", "work_key", "work_key.pub", "work_key-cert.pub", "agent_only.pub", "removed-drone-20261004/id_drone", "authorized_keys2", "id_token"} {
|
||||
sshkeys.KeyFile, sshkeys.KeyFile + ".pub", "id_ed25519", "id_ed25519.pub", "work_key", "work_key.pub", "work_key-cert.pub", "agent_only.pub", "removed-drone-20261004/id_drone", "authorized_keys2", "id_token"} {
|
||||
if !exists(filepath.Join(dir, rel)) {
|
||||
t.Errorf("%s was moved; it is known", rel)
|
||||
}
|
||||
|
||||
@@ -62,6 +62,16 @@ func TestEveryFileTakesItsTextFromATemplate(t *testing.T) {
|
||||
if packs != 1 {
|
||||
t.Errorf("the build packs files/ into %d archives, not one", packs)
|
||||
}
|
||||
// The key keeper is this module's published package, taken by series (ADR 0634 §1).
|
||||
published := false
|
||||
for _, a := range m.Build.Artifacts {
|
||||
if a["name"] == "sshkeys" && a["kind"] == "package" && a["series"] == "0.1" {
|
||||
published = true
|
||||
}
|
||||
}
|
||||
if !published {
|
||||
t.Errorf("no package artifact sshkeys of series 0.1 in the build")
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 955, area S2: the module keeps the keys state and requires the forge's account provision. It declares
|
||||
|
||||
@@ -20,3 +20,9 @@ require (
|
||||
golang.org/x/sys v0.48.0 // indirect
|
||||
golang.org/x/time v0.16.0 // indirect
|
||||
)
|
||||
|
||||
require git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys v0.0.0-00010101000000-000000000000
|
||||
|
||||
replace git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys => ./sshkeys
|
||||
|
||||
require git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000 // indirect
|
||||
|
||||
@@ -106,6 +106,15 @@
|
||||
"name": "files",
|
||||
"kind": "archive",
|
||||
"from": "files"
|
||||
},
|
||||
{
|
||||
"name": "sshkeys",
|
||||
"kind": "package",
|
||||
"language": "go",
|
||||
"paths": [
|
||||
"sshkeys"
|
||||
],
|
||||
"series": "0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
module git.novox.be/novox/mesh-catalog/modules/ssh-client/sshkeys
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
@@ -0,0 +1,131 @@
|
||||
// Package sshkeys is ssh-client's own published package: the key keeper every module that reaches the forge over SSH
|
||||
// shares (ssh-client for a machine's accounts, a build module for its own; design 47 R4c-3, ADR 0466 §2; issue 1469)
|
||||
// and the reading of a public key line. A module takes it by series (ADR 0634 §1: the owner's package).
|
||||
package sshkeys
|
||||
|
||||
// The key this machine's account uses to reach the forge over SSH (novox/hq ADR 0399, ADR 0400; issue 955,
|
||||
// area S1). It is made here, on the machine, once: ssh-keygen writes ~/.ssh/mesh_ed25519 only when it is
|
||||
// missing, so the private half never leaves the machine and an existing key is never replaced. The public
|
||||
// half and its fingerprint go into this module's `keys` state, one entry per account under <node>.<account>,
|
||||
// for the forge's provider to register. The tools bundle is the one process of this module, so the keeper
|
||||
// runs inside it beside the tools (main.go), the way claude-code reports what it holds.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
sdk "git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
// KeyFile is the key's name under ~/.ssh: never one of the operator's own id_* files.
|
||||
const KeyFile = "mesh_ed25519"
|
||||
|
||||
// The kinds of key (ADR 0400 §1, ADR 0466 §2): a machine's account's own, and a build module's.
|
||||
const KindMachine = "machine"
|
||||
|
||||
// KindBuilder is the kind of a build module's key, kept with the Delivery domain's names.
|
||||
const KindBuilder = forgekey.KindBuilder
|
||||
|
||||
// KeyRetry is how soon it looks again after a failure.
|
||||
const KeyRetry = 30 * time.Second
|
||||
|
||||
// KeyRound is how often the keeper looks again.
|
||||
const KeyRound = 10 * time.Minute
|
||||
|
||||
// KeyEntry is one value of the keys state. It carries the public half only.
|
||||
type KeyEntry struct {
|
||||
Node string `json:"node"`
|
||||
Account string `json:"account"`
|
||||
Kind string `json:"kind"`
|
||||
Public string `json:"public"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
At string `json:"at"`
|
||||
}
|
||||
|
||||
// KeyPutter is the keys state, as far as the keeper needs it.
|
||||
type KeyPutter interface {
|
||||
Put(key string, value any) (uint64, error)
|
||||
}
|
||||
|
||||
// KeyKeeper keeps one account's key and its entry.
|
||||
type KeyKeeper struct {
|
||||
Home, Node, Account string
|
||||
Kind string // KindMachine or KindBuilder
|
||||
Run sdk.Runner
|
||||
State KeyPutter
|
||||
Now func() time.Time
|
||||
// After waits between rounds; time.After when nil. A test sets it to see which pause was chosen.
|
||||
After func(time.Duration) <-chan time.Time
|
||||
|
||||
reported string // the public half and fingerprint last put
|
||||
}
|
||||
|
||||
func (k *KeyKeeper) path() string { return filepath.Join(k.Home, ".ssh", KeyFile) }
|
||||
|
||||
// Once makes the key when it is missing and puts the entry when it differs from the one last put.
|
||||
func (k *KeyKeeper) Once(ctx context.Context) error {
|
||||
if err := k.ensure(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
raw, err := os.ReadFile(k.path() + ".pub")
|
||||
if err != nil {
|
||||
return fmt.Errorf("the public half: %w", err)
|
||||
}
|
||||
pk, err := ParseKeyLine(string(raw))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s.pub: %w", k.path(), err)
|
||||
}
|
||||
public := strings.TrimSpace(string(raw))
|
||||
if sig := public + "|" + pk.Fingerprint; sig == k.reported {
|
||||
return nil
|
||||
}
|
||||
entry := KeyEntry{Node: k.Node, Account: k.Account, Kind: k.Kind, Public: public,
|
||||
Fingerprint: pk.Fingerprint, At: k.Now().UTC().Format(time.RFC3339)}
|
||||
if _, err := k.State.Put(k.Node+"."+k.Account, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
k.reported = public + "|" + pk.Fingerprint
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensure makes the pair when the private half is missing. Never over an existing key.
|
||||
func (k *KeyKeeper) ensure(ctx context.Context) error {
|
||||
if _, err := os.Lstat(k.path()); err == nil {
|
||||
return nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(k.path()), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
r := k.Run(ctx, nil, "ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", k.Account+"@"+k.Node+" (mesh)", "-f", k.path())
|
||||
if r.Status != 0 || r.Err != "" {
|
||||
return fmt.Errorf("ssh-keygen: %s", forgekey.Said(r.Err+"\n"+r.Stderr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Keep runs Once now and every KeyRound, reporting a failure through say and trying again at the next round.
|
||||
func (k *KeyKeeper) Keep(ctx context.Context, say func(string, ...any)) {
|
||||
for {
|
||||
pause := KeyRound
|
||||
if err := k.Once(ctx); err != nil {
|
||||
say("the forge key: %v", err)
|
||||
pause = KeyRetry // the state or the bus's grant may arrive after the process
|
||||
}
|
||||
after := k.After
|
||||
if after == nil {
|
||||
after = time.After
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-after(pause):
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package sshkeys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdk "git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
// Issue 955, area S1: the key the machine's account makes for the forge, and its entry in the keys state.
|
||||
|
||||
type fakeState struct{ puts map[string][]byte }
|
||||
|
||||
func (f *fakeState) Put(key string, value any) (uint64, error) {
|
||||
raw, _ := json.Marshal(value)
|
||||
if f.puts == nil {
|
||||
f.puts = map[string][]byte{}
|
||||
}
|
||||
f.puts[key] = raw
|
||||
return uint64(len(f.puts)), nil
|
||||
}
|
||||
|
||||
// ed25519Line is a public key line with a well-formed ed25519 blob.
|
||||
func ed25519Line(seed byte, comment string) string {
|
||||
blob := []byte{}
|
||||
for _, part := range [][]byte{[]byte("ssh-ed25519"), make([]byte, 32)} {
|
||||
part2 := append([]byte(nil), part...)
|
||||
if len(part2) == 32 {
|
||||
part2[0] = seed
|
||||
}
|
||||
l := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(l, uint32(len(part2)))
|
||||
blob = append(append(blob, l...), part2...)
|
||||
}
|
||||
return "ssh-ed25519 " + base64.StdEncoding.EncodeToString(blob) + " " + comment
|
||||
}
|
||||
|
||||
// keygenRunner stands in for ssh-keygen -t ed25519: it writes the pair the command names.
|
||||
func keygenRunner(calls *[][]string) sdk.Runner {
|
||||
return func(ctx context.Context, env []string, name string, args ...string) sdk.Ran {
|
||||
*calls = append(*calls, append([]string{name}, args...))
|
||||
var file, comment string
|
||||
for i, a := range args {
|
||||
switch a {
|
||||
case "-f":
|
||||
file = args[i+1]
|
||||
case "-C":
|
||||
comment = args[i+1]
|
||||
}
|
||||
}
|
||||
if name != "ssh-keygen" || file == "" {
|
||||
return sdk.Ran{Status: 1, Stderr: "unexpected"}
|
||||
}
|
||||
os.WriteFile(file, []byte("-----BEGIN OPENSSH PRIVATE KEY-----\nPRIVATE-SECRET\n"), 0o600)
|
||||
os.WriteFile(file+".pub", []byte(ed25519Line(7, comment)+"\n"), 0o644)
|
||||
return sdk.Ran{}
|
||||
}
|
||||
}
|
||||
|
||||
func newKeeper(t *testing.T, home string, st *fakeState, calls *[][]string) *KeyKeeper {
|
||||
return &KeyKeeper{Home: home, Node: "g14", Account: "nox", Kind: KindMachine, Run: keygenRunner(calls), State: st,
|
||||
Now: func() time.Time { return time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC) }}
|
||||
}
|
||||
|
||||
func TestKeeperMakesTheKeyAndWritesItsEntry(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
st, calls := &fakeState{}, &[][]string{}
|
||||
if err := newKeeper(t, home, st, calls).Once(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, ok := st.puts["g14.nox"]
|
||||
if !ok || len(st.puts) != 1 {
|
||||
t.Fatalf("entries = %v, want one under g14.nox", st.puts)
|
||||
}
|
||||
var e map[string]string
|
||||
json.Unmarshal(raw, &e)
|
||||
if e["node"] != "g14" || e["account"] != "nox" || e["kind"] != "machine" || e["at"] != "2026-10-16T12:00:00Z" {
|
||||
t.Errorf("entry = %v", e)
|
||||
}
|
||||
if !strings.HasPrefix(e["public"], "ssh-ed25519 ") || !strings.HasSuffix(e["public"], " nox@g14 (mesh)") {
|
||||
t.Errorf("public = %q", e["public"])
|
||||
}
|
||||
if !strings.HasPrefix(e["fingerprint"], "SHA256:") {
|
||||
t.Errorf("fingerprint = %q", e["fingerprint"])
|
||||
}
|
||||
if strings.Contains(strings.ToUpper(string(raw)), "PRIVATE") {
|
||||
t.Errorf("the entry carries private material: %s", raw)
|
||||
}
|
||||
cmd := strings.Join((*calls)[0], " ")
|
||||
for _, want := range []string{"-t ed25519", "-N ", "-C nox@g14 (mesh)", filepath.Join(home, ".ssh", "mesh_ed25519")} {
|
||||
if !strings.Contains(cmd, want) {
|
||||
t.Errorf("ssh-keygen call %q lacks %q", cmd, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeeperKeepsAnExistingKeyByteForByte(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
os.MkdirAll(filepath.Join(home, ".ssh"), 0o700)
|
||||
priv, pub := filepath.Join(home, ".ssh", "mesh_ed25519"), filepath.Join(home, ".ssh", "mesh_ed25519.pub")
|
||||
os.WriteFile(priv, []byte("mine"), 0o600)
|
||||
os.WriteFile(pub, []byte(ed25519Line(9, "nox@g14 (mesh)")+"\n"), 0o644)
|
||||
st, calls := &fakeState{}, &[][]string{}
|
||||
k := newKeeper(t, home, st, calls)
|
||||
if err := k.Once(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(*calls) != 0 {
|
||||
t.Errorf("ssh-keygen ran over an existing key: %v", *calls)
|
||||
}
|
||||
if b, _ := os.ReadFile(priv); string(b) != "mine" {
|
||||
t.Errorf("private file changed: %q", b)
|
||||
}
|
||||
if _, ok := st.puts["g14.nox"]; !ok {
|
||||
t.Errorf("no entry for the existing key")
|
||||
}
|
||||
// A second round with nothing changed writes nothing.
|
||||
delete(st.puts, "g14.nox")
|
||||
k.Once(context.Background())
|
||||
if len(st.puts) != 0 {
|
||||
t.Errorf("an unchanged key was put again")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeeperNeverTouchesTheOperatorsOwnKeys(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
os.MkdirAll(filepath.Join(home, ".ssh"), 0o700)
|
||||
own := filepath.Join(home, ".ssh", "id_ed25519")
|
||||
os.WriteFile(own, []byte("operator"), 0o600)
|
||||
st, calls := &fakeState{}, &[][]string{}
|
||||
newKeeper(t, home, st, calls).Once(context.Background())
|
||||
if b, _ := os.ReadFile(own); string(b) != "operator" {
|
||||
t.Errorf("the operator's key changed")
|
||||
}
|
||||
}
|
||||
|
||||
// 1469/AC1: an empty home, kind builder, account mesh-build-go: ssh-keygen once, for the one file, and the entry
|
||||
// carries the kind, the public half and its fingerprint.
|
||||
func TestBuilderKeyIsMadeOnceAndPutWithItsKind(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
st, calls := &fakeState{}, &[][]string{}
|
||||
k := newKeeper(t, home, st, calls)
|
||||
k.Account, k.Kind = "mesh-build-go", KindBuilder
|
||||
if err := k.Once(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(*calls) != 1 || !strings.Contains(strings.Join((*calls)[0], " "), filepath.Join(home, ".ssh", "mesh_ed25519")) {
|
||||
t.Errorf("ssh-keygen calls = %v, want one for mesh_ed25519 in the home", *calls)
|
||||
}
|
||||
var e KeyEntry
|
||||
if err := json.Unmarshal(st.puts["g14.mesh-build-go"], &e); err != nil {
|
||||
t.Fatalf("entry under g14.mesh-build-go: %v (%v)", err, st.puts)
|
||||
}
|
||||
pub, _ := os.ReadFile(filepath.Join(home, ".ssh", "mesh_ed25519.pub"))
|
||||
pk, _ := ParseKeyLine(string(pub))
|
||||
if e.Kind != "builder" || e.Public != strings.TrimSpace(string(pub)) || e.Fingerprint != pk.Fingerprint || e.Fingerprint == "" {
|
||||
t.Errorf("entry = %+v", e)
|
||||
}
|
||||
}
|
||||
|
||||
// 1469/AC2: an existing key, Once twice: ssh-keygen never, one put.
|
||||
func TestExistingKeyIsPutOnceAcrossTwoRounds(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
os.MkdirAll(filepath.Join(home, ".ssh"), 0o700)
|
||||
os.WriteFile(filepath.Join(home, ".ssh", "mesh_ed25519"), []byte("mine"), 0o600)
|
||||
os.WriteFile(filepath.Join(home, ".ssh", "mesh_ed25519.pub"), []byte(ed25519Line(9, "x")+"\n"), 0o644)
|
||||
st, calls := &countState{}, &[][]string{}
|
||||
k := newKeeper(t, home, &fakeState{}, calls)
|
||||
k.State = st
|
||||
for i := 0; i < 2; i++ {
|
||||
if err := k.Once(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(*calls) != 0 || st.n != 1 {
|
||||
t.Errorf("ssh-keygen calls = %d, puts = %d, want 0 and 1", len(*calls), st.n)
|
||||
}
|
||||
}
|
||||
|
||||
type countState struct{ n int }
|
||||
|
||||
func (c *countState) Put(string, any) (uint64, error) { c.n++; return uint64(c.n), nil }
|
||||
|
||||
// 1469/AC3: the entry's JSON is ssh-client's own (mesh-catalog modules/ssh-client keykeeper_test.go's keys
|
||||
// entry: node, account, kind, public, fingerprint, at, in that order); mesh-catalog's gitea provider reads it.
|
||||
func TestKeyEntryJSONIsSSHClients(t *testing.T) {
|
||||
raw, err := json.Marshal(KeyEntry{Node: "g14", Account: "nox", Kind: "machine", Public: "ssh-ed25519 AAAA nox@g14 (mesh)",
|
||||
Fingerprint: "SHA256:abc", At: "2026-10-16T12:00:00Z"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := `{"node":"g14","account":"nox","kind":"machine","public":"ssh-ed25519 AAAA nox@g14 (mesh)","fingerprint":"SHA256:abc","at":"2026-10-16T12:00:00Z"}`
|
||||
if string(raw) != want {
|
||||
t.Errorf("entry JSON = %s\nwant %s", raw, want)
|
||||
}
|
||||
}
|
||||
|
||||
type failState struct{}
|
||||
|
||||
func (failState) Put(string, any) (uint64, error) { return 0, errors.New("no grant yet") }
|
||||
|
||||
// 1469/AC4: a failing put is said, and the next look is after KeyRetry, not KeyRound.
|
||||
func TestKeepSaysAFailedPutAndRetriesSoon(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
k := newKeeper(t, home, &fakeState{}, &[][]string{})
|
||||
k.State = failState{}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var said []string
|
||||
var pauses []time.Duration
|
||||
k.After = func(d time.Duration) <-chan time.Time {
|
||||
pauses = append(pauses, d)
|
||||
cancel()
|
||||
return make(chan time.Time)
|
||||
}
|
||||
k.Keep(ctx, func(f string, a ...any) { said = append(said, fmt.Sprintf(f, a...)) })
|
||||
if len(said) != 1 || !strings.Contains(said[0], "no grant yet") {
|
||||
t.Errorf("said = %v", said)
|
||||
}
|
||||
if len(pauses) != 1 || pauses[0] != KeyRetry {
|
||||
t.Errorf("pauses = %v, want [%v]", pauses, KeyRetry)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package sshkeys
|
||||
|
||||
// Keys, by their public half only. A fingerprint is computed here from the public key's bytes, as
|
||||
// ssh-keygen -l does (SHA256 of the key blob, unpadded base64); a private key is never read by this
|
||||
// process — its first line, the PEM header, says it is one, and ssh-keygen is asked about it.
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// KeyTypes are the public key algorithms an authorized_keys or known_hosts line can carry.
|
||||
var KeyTypes = map[string]bool{
|
||||
"ssh-ed25519": true, "ssh-rsa": true, "ssh-dss": true,
|
||||
"ecdsa-sha2-nistp256": true, "ecdsa-sha2-nistp384": true, "ecdsa-sha2-nistp521": true,
|
||||
"sk-ssh-ed25519@openssh.com": true, "sk-ecdsa-sha2-nistp256@openssh.com": true,
|
||||
}
|
||||
|
||||
// PublicKey is one public key as a line carries it.
|
||||
type PublicKey struct {
|
||||
Type string `json:"type"`
|
||||
Bits int `json:"bits"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
Comment string `json:"comment"`
|
||||
Options string `json:"options,omitempty"`
|
||||
Weak string `json:"weak,omitempty"`
|
||||
}
|
||||
|
||||
// ParseKeyLine reads one authorized_keys line (options, type, key, comment) or a public key file's.
|
||||
func ParseKeyLine(line string) (PublicKey, error) {
|
||||
fields := fieldsQuoted(strings.TrimSpace(line))
|
||||
for i, f := range fields {
|
||||
if !KeyTypes[f] || i+1 >= len(fields) {
|
||||
continue
|
||||
}
|
||||
k := PublicKey{Type: f, Options: strings.Join(fields[:i], " "), Comment: strings.Join(fields[i+2:], " ")}
|
||||
blob, err := base64.StdEncoding.DecodeString(fields[i+1])
|
||||
if err != nil {
|
||||
return k, fmt.Errorf("the key after %s is not base64", f)
|
||||
}
|
||||
sum := sha256.Sum256(blob)
|
||||
k.Fingerprint = "SHA256:" + base64.RawStdEncoding.EncodeToString(sum[:])
|
||||
k.Bits = bitsOf(f, blob)
|
||||
switch {
|
||||
case f == "ssh-dss":
|
||||
k.Weak = "DSA: refused by current OpenSSH"
|
||||
case f == "ssh-rsa" && k.Bits > 0 && k.Bits < 3072:
|
||||
k.Weak = fmt.Sprintf("RSA of %d bits: below 3072", k.Bits)
|
||||
}
|
||||
return k, nil
|
||||
}
|
||||
return PublicKey{}, fmt.Errorf("no public key on this line")
|
||||
}
|
||||
|
||||
// fieldsQuoted splits on spaces outside double quotes, as sshd reads an options field.
|
||||
func fieldsQuoted(s string) []string {
|
||||
var out []string
|
||||
var cur strings.Builder
|
||||
quoted := false
|
||||
for _, ch := range s {
|
||||
switch {
|
||||
case ch == '"':
|
||||
quoted = !quoted
|
||||
cur.WriteRune(ch)
|
||||
case (ch == ' ' || ch == '\t') && !quoted:
|
||||
if cur.Len() > 0 {
|
||||
out = append(out, cur.String())
|
||||
cur.Reset()
|
||||
}
|
||||
default:
|
||||
cur.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
if cur.Len() > 0 {
|
||||
out = append(out, cur.String())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// bitsOf reads a key's size from its blob: the RSA modulus, the curve, or ed25519's fixed 256.
|
||||
func bitsOf(kind string, blob []byte) int {
|
||||
strs := [][]byte{}
|
||||
for len(blob) >= 4 {
|
||||
n := binary.BigEndian.Uint32(blob)
|
||||
if int(n) > len(blob)-4 {
|
||||
break
|
||||
}
|
||||
strs = append(strs, blob[4:4+n])
|
||||
blob = blob[4+n:]
|
||||
}
|
||||
switch {
|
||||
case strings.Contains(kind, "ed25519"):
|
||||
return 256
|
||||
case kind == "ssh-rsa" && len(strs) >= 3:
|
||||
return new(big.Int).SetBytes(strs[2]).BitLen()
|
||||
case kind == "ssh-dss" && len(strs) >= 2:
|
||||
return new(big.Int).SetBytes(strs[1]).BitLen()
|
||||
case strings.Contains(kind, "nistp256"):
|
||||
return 256
|
||||
case strings.Contains(kind, "nistp384"):
|
||||
return 384
|
||||
case strings.Contains(kind, "nistp521"):
|
||||
return 521
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user