fix: PVE role/user idempotency + init pre-staging instructions
- createPVERole: use grep -qF + fallback to pveum role mod (was broken by single-quote-in-grep pattern: grep -q '^'OrcaOperator'') - createPVEUser: same idempotency fix (grep -qF + fallback to mod) - orca init: prints ssh-copy-id instructions with the orca public key path after generating the SSH keypair - docs/uat.md: removed manual pre-staging (ssh-keygen, ssh-copy-id with operator key, host-key fingerprint pinning). orca init handles key generation; node join uses the orca key by default; TOFU is automatic. Updated node join examples to not pass --ssh-key or --host-key-fingerprint. ---ci--- project: orca status: fix ---/ci---
This commit is contained in:
+26
-35
@@ -21,52 +21,45 @@ Use `--type linux` for all remote nodes. Proxmox-specific claims
|
|||||||
(`doctor proxmox`, PVE role, sudoers) are **skipped** in this path.
|
(`doctor proxmox`, PVE role, sudoers) are **skipped** in this path.
|
||||||
The signoff script reports exercised vs. skipped claims.
|
The signoff script reports exercised vs. skipped claims.
|
||||||
|
|
||||||
### Pre-staging
|
|
||||||
|
|
||||||
1. Build orca from the v0.13 tag:
|
|
||||||
```sh
|
|
||||||
git clone https://git.cloudinit.dev/coreci/orca.git
|
|
||||||
cd orca && git checkout v0.12.13
|
|
||||||
make build
|
|
||||||
# binary is at bin/orca
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Generate the orca SSH keypair on the lead:
|
|
||||||
```sh
|
|
||||||
ssh-keygen -t ed25519 -f ~/.ssh/orca_ed25519 -N ""
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Pre-stage the orca public key on pve01 and worker01:
|
|
||||||
```sh
|
|
||||||
ssh-copy-id -i ~/.ssh/orca_ed25519.pub root@pve01
|
|
||||||
ssh-copy-id -i ~/.ssh/orca_ed25519.pub root@worker01
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Pin host-key fingerprints (optional but recommended):
|
|
||||||
```sh
|
|
||||||
ssh-keyscan pve01 | ssh-keygen -lf -
|
|
||||||
ssh-keyscan worker01 | ssh-keygen -lf -
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step-by-step UAT
|
## Step-by-step UAT
|
||||||
|
|
||||||
### Step 1: Initialize the cluster
|
### Step 1: Install orca + initialize the cluster
|
||||||
|
|
||||||
|
Install orca (1-liner):
|
||||||
|
```sh
|
||||||
|
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/branch/main/scripts/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Initialize the cluster:
|
||||||
```sh
|
```sh
|
||||||
export ORCA_HOME=~/orca-uat
|
export ORCA_HOME=~/orca-uat
|
||||||
orca init
|
orca init
|
||||||
```
|
```
|
||||||
|
|
||||||
**Expected**: cluster directory created, CA cert generated, localhost node registered.
|
**Expected**: orca init creates:
|
||||||
|
- CA cert + server cert
|
||||||
|
- SSH keypair (orca_ssh_key + orca_ssh_key.pub)
|
||||||
|
- known_hosts file (empty, for TOFU capture)
|
||||||
|
- Master key (for secrets encryption)
|
||||||
|
- Traefik data-plane ingress (binary + systemd unit + config)
|
||||||
|
- Localhost node registered
|
||||||
|
|
||||||
|
**Pre-staging remote nodes**: `orca init` prints the orca public key path.
|
||||||
|
Deploy it to each remote host before joining:
|
||||||
|
```sh
|
||||||
|
ssh-copy-id -i $ORCA_HOME/orca_ssh_key.pub root@pve01
|
||||||
|
ssh-copy-id -i $ORCA_HOME/orca_ssh_key.pub root@worker01
|
||||||
|
```
|
||||||
|
|
||||||
|
The TOFU host-key capture is automatic — no manual fingerprint pinning needed.
|
||||||
|
The first SSH connection captures and stores the remote host key.
|
||||||
|
|
||||||
### Step 2: Onboard the Proxmox host
|
### Step 2: Onboard the Proxmox host
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
orca node join --type proxmox \
|
orca node join --type proxmox \
|
||||||
--host pve01 \
|
--host pve01 \
|
||||||
--ssh-user root \
|
--ssh-user root
|
||||||
--ssh-key ~/.ssh/orca_ed25519 \
|
|
||||||
--host-key-fingerprint SHA256:<fingerprint>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Expected**: SSH bootstrap succeeds, orca user created, PVE role assigned, node registered as `ready` with `kind=proxmox`.
|
**Expected**: SSH bootstrap succeeds, orca user created, PVE role assigned, node registered as `ready` with `kind=proxmox`.
|
||||||
@@ -76,9 +69,7 @@ orca node join --type proxmox \
|
|||||||
```sh
|
```sh
|
||||||
orca node join --type linux \
|
orca node join --type linux \
|
||||||
--host worker01 \
|
--host worker01 \
|
||||||
--ssh-user root \
|
--ssh-user root
|
||||||
--ssh-key ~/.ssh/orca_ed25519 \
|
|
||||||
--host-key-fingerprint SHA256:<fingerprint>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Expected**: SSH bootstrap succeeds, orca user created, drift-events dir created, node registered as `ready` with `kind=linux`.
|
**Expected**: SSH bootstrap succeeds, orca user created, drift-events dir created, node registered as `ready` with `kind=linux`.
|
||||||
|
|||||||
@@ -158,6 +158,8 @@ func runInit(out interface{ Write([]byte) (int, error) }) error {
|
|||||||
summary.Steps = append(summary.Steps, stepResult{Label: "ssh-key", Status: "ok", Detail: sshKeyFp[:min(16, len(sshKeyFp))] + "..."})
|
summary.Steps = append(summary.Steps, stepResult{Label: "ssh-key", Status: "ok", Detail: sshKeyFp[:min(16, len(sshKeyFp))] + "..."})
|
||||||
if !jsonOutput {
|
if !jsonOutput {
|
||||||
fmt.Fprintf(out, "\xe2\x9c\x93 SSH keypair provisioned: fp=%s\n", sshKeyFp[:min(16, len(sshKeyFp))]+"...")
|
fmt.Fprintf(out, "\xe2\x9c\x93 SSH keypair provisioned: fp=%s\n", sshKeyFp[:min(16, len(sshKeyFp))]+"...")
|
||||||
|
fmt.Fprintf(out, "\n To onboard remote nodes, deploy the orca public key first:\n")
|
||||||
|
fmt.Fprintf(out, " ssh-copy-id -i %s root@<remote-host>\n\n", certpaths.SSHPubPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4b: known_hosts file (empty, 0600). Without this, the TOFU
|
// Step 4b: known_hosts file (empty, 0600). Without this, the TOFU
|
||||||
|
|||||||
@@ -467,10 +467,12 @@ func createLinuxUser(user string) error {
|
|||||||
// createPVERole creates the OrcaOperator PVE role if it doesn't exist.
|
// createPVERole creates the OrcaOperator PVE role if it doesn't exist.
|
||||||
// Idempotent: probes `pveum role list` before `pveum role add`.
|
// Idempotent: probes `pveum role list` before `pveum role add`.
|
||||||
func createPVERole(role string) error {
|
func createPVERole(role string) error {
|
||||||
// F10c: shellQuote the role (validated upstream, but defense-in-depth).
|
// Idempotent: check if the role already exists before creating.
|
||||||
|
// Use pveum role list with grep -qF (fixed string, not regex) to
|
||||||
|
// avoid shell-quoting issues with single quotes inside the pattern.
|
||||||
cmd := fmt.Sprintf(
|
cmd := fmt.Sprintf(
|
||||||
"pveum role list 2>/dev/null | grep -q '^%s' || pveum role add %s --privs '%s'",
|
"pveum role list 2>/dev/null | grep -qF %s && exit 0 || pveum role add %s --privs '%s' 2>/dev/null || pveum role mod %s --privs '%s'",
|
||||||
shellQuote(role), shellQuote(role), OrcaOperatorPrivileges,
|
shellQuote(role), shellQuote(role), OrcaOperatorPrivileges, shellQuote(role), OrcaOperatorPrivileges,
|
||||||
)
|
)
|
||||||
if _, err := runRemote(cmd); err != nil {
|
if _, err := runRemote(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -483,11 +485,10 @@ func createPVERole(role string) error {
|
|||||||
// Uses @pam realm (AD-019) since orca creates a Linux system user.
|
// Uses @pam realm (AD-019) since orca creates a Linux system user.
|
||||||
func createPVEUser(user string) error {
|
func createPVEUser(user string) error {
|
||||||
pveUserID := user + "@pam"
|
pveUserID := user + "@pam"
|
||||||
// F10c: shellQuote the PVE user id (validated upstream, but
|
// Idempotent: check if user exists, create if not, update comment if exists.
|
||||||
// defense-in-depth).
|
|
||||||
cmd := fmt.Sprintf(
|
cmd := fmt.Sprintf(
|
||||||
"pveum user list 2>/dev/null | grep -q %s || pveum user add %s -comment 'Orca automation user'",
|
"pveum user list 2>/dev/null | grep -qF %s && exit 0 || pveum user add %s -comment 'Orca automation user' 2>/dev/null || pveum user mod %s -comment 'Orca automation user'",
|
||||||
shellQuote(pveUserID), shellQuote(pveUserID),
|
shellQuote(pveUserID), shellQuote(pveUserID), shellQuote(pveUserID),
|
||||||
)
|
)
|
||||||
if _, err := runRemote(cmd); err != nil {
|
if _, err := runRemote(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user