Merge: t-paliad-151 fix — base64-decode PALIADIN_SSH_PRIVATE_KEY env var
Dokploy's .env mechanism truncates multi-line env vars to first line. Empirically: the multi-line PEM arrived as just `-----BEGIN OPENSSH PRIVATE KEY-----\n` (36 bytes) inside the container, ssh -i failed with `Load key: error in libcrypto`. Go now decodes the env value as either raw PEM (multi-line) or base64-encoded PEM. Whitespace inside base64 stripped before decode. Dokploy secret already updated to the base64 form alongside this merge. Refs m/paliad#12
This commit is contained in:
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
// Embed Go's IANA tz database into the binary so time.LoadLocation works
|
// Embed Go's IANA tz database into the binary so time.LoadLocation works
|
||||||
@@ -262,7 +264,15 @@ func buildPaliadinRemoteConfig(host string) (services.RemotePaliadinConfig, erro
|
|||||||
cfg.SSHPort = n
|
cfg.SSHPort = n
|
||||||
}
|
}
|
||||||
|
|
||||||
keyPath, err := writeSecretFile("paliadin-id_ed25519-", os.Getenv("PALIADIN_SSH_PRIVATE_KEY"), 0o600)
|
// Dokploy stores compose env vars in a single-line .env file: multi-line
|
||||||
|
// PEM bodies get truncated to the first line. Base64-encode the
|
||||||
|
// private key in the secret to survive that round-trip; here we
|
||||||
|
// detect base64 vs raw PEM and decode either way.
|
||||||
|
keyBlob, err := decodePaliadinPrivateKey(os.Getenv("PALIADIN_SSH_PRIVATE_KEY"))
|
||||||
|
if err != nil {
|
||||||
|
return cfg, fmt.Errorf("PALIADIN_SSH_PRIVATE_KEY: %w", err)
|
||||||
|
}
|
||||||
|
keyPath, err := writeSecretFile("paliadin-id_ed25519-", keyBlob, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, fmt.Errorf("PALIADIN_SSH_PRIVATE_KEY: %w", err)
|
return cfg, fmt.Errorf("PALIADIN_SSH_PRIVATE_KEY: %w", err)
|
||||||
}
|
}
|
||||||
@@ -283,6 +293,48 @@ func buildPaliadinRemoteConfig(host string) (services.RemotePaliadinConfig, erro
|
|||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decodePaliadinPrivateKey accepts either a raw PEM (multi-line) or a
|
||||||
|
// base64-encoded PEM. Returns the raw PEM bytes ready to write to a
|
||||||
|
// keyfile. Empty input → ("", nil) so the caller can distinguish
|
||||||
|
// "secret not set" from "decode failed".
|
||||||
|
//
|
||||||
|
// Why base64: Dokploy stores compose env vars in a one-line-per-key
|
||||||
|
// .env file, which silently truncates multi-line values to their first
|
||||||
|
// line. Empirically, a multi-line `-----BEGIN OPENSSH PRIVATE KEY-----`
|
||||||
|
// arrived inside the container as just the BEGIN header (36 bytes).
|
||||||
|
// Base64-encoding the key in the Dokploy secret survives that
|
||||||
|
// round-trip. We still accept raw PEM for local-dev convenience.
|
||||||
|
func decodePaliadinPrivateKey(blob string) (string, error) {
|
||||||
|
blob = strings.TrimSpace(blob)
|
||||||
|
if blob == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
// Raw PEM: starts with ----- and contains a newline. Use as-is.
|
||||||
|
if strings.HasPrefix(blob, "-----") && strings.Contains(blob, "\n") {
|
||||||
|
return blob + "\n", nil
|
||||||
|
}
|
||||||
|
// Otherwise treat as base64. Strip any whitespace OpenSSH keygen
|
||||||
|
// helpers might insert (line breaks every 64 chars in some tools).
|
||||||
|
clean := strings.Map(func(r rune) rune {
|
||||||
|
if r == ' ' || r == '\n' || r == '\r' || r == '\t' {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, blob)
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(clean)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("not raw PEM (no newline) and base64 decode failed: %w", err)
|
||||||
|
}
|
||||||
|
out := string(decoded)
|
||||||
|
if !strings.HasPrefix(out, "-----BEGIN") {
|
||||||
|
return "", fmt.Errorf("decoded body does not look like a PEM key (no -----BEGIN prefix)")
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(out, "\n") {
|
||||||
|
out += "\n"
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// writeSecretFile writes blob to a tmpfile with the given mode and
|
// writeSecretFile writes blob to a tmpfile with the given mode and
|
||||||
// returns its path. Returns ("", nil) when blob is empty so callers
|
// returns its path. Returns ("", nil) when blob is empty so callers
|
||||||
// can distinguish "not set" from real I/O errors.
|
// can distinguish "not set" from real I/O errors.
|
||||||
|
|||||||
Reference in New Issue
Block a user