Compare commits

..

5 Commits

Author SHA1 Message Date
Jon Chery 50c4e910ed fix(P14): master key rotation (REQ-129, F12, C-30)
---ci---
project: orca
phase: 14
milestone: v0.12
status: execute
---/ci---

orca secrets rotate-master: generates new master key, re-encrypts all
namespace secrets under new key, saves new key. --dry-run reports
affected namespaces. Atomic per-namespace; automatic rollback to old
key on any failure (C-30). Fixed unused nsKey in get+list (pre-existing
vet issue). Build + vet + tests green.
2026-08-07 11:24:19 +00:00
Jon Chery 0d5ff663b4 fix(P13): step-ca /tmp hardening (REQ-128, F10)
---ci---
project: orca
phase: 13
milestone: v0.12
status: execute
---/ci---

step-ca cert/key temp files moved from world-readable /tmp/orca-* to
/etc/orca/step-tmp/orca-* (0700). mkdir + chmod 700 before writing.
Fixes both stepca.go and spiffe.go. All tests updated + pass.
2026-08-07 11:22:07 +00:00
Jon Chery 7f81042abd fix(P12): backup symlink validation (REQ-127, F7)
---ci---
project: orca
phase: 12
milestone: v0.12
status: execute
---/ci---

Restore validates Linkname: rejects absolute, .. traversal, and
links escaping target dir. Prevents symlink-to-/etc/shadow attacks.
2 regression tests with crafted tarballs. Build + vet green.
2026-08-07 11:21:19 +00:00
Jon Chery d7dc2d2aad fix(P11): SVID chain validation (REQ-126, F9)
---ci---
project: orca
phase: 11
milestone: v0.12
status: execute
---/ci---

VerifySVIDWithChain: validates the full cert chain against the CA pool
+ checks the SPIFFE URI SAN. Rejects certs from unknown CAs even with
correct URI (F9). VerifySVID retained for backward compat (mTLS
callers that already verified the chain). 2 new tests. Build + vet green.
2026-08-07 11:20:10 +00:00
Jon Chery a627d0ee6d docs(checkpoint): P09+P10 shipped (daemon auth + audit tamper-evidence)
---ci---
project: orca
phase: 10
milestone: v0.12
status: complete
---/ci---
2026-08-07 11:18:52 +00:00
8 changed files with 320 additions and 26 deletions
+5 -5
View File
@@ -1,16 +1,16 @@
{
"phase": 5,
"phase": 10,
"stage": "complete",
"milestone": "v0.12",
"milestone_slug": "security-hardening",
"phase_role": "execution",
"attempts": 0,
"updated_at": "2026-08-07T11:03:00Z",
"updated_at": "2026-08-07T11:19:00Z",
"milestone_complete": false,
"previous_milestone": "v0.11",
"wave": "B (P06 ACL rewrite, P07 password removal, P08 master key seal) next",
"phases_shiped": ["P0","P1","P2","P3","P4","P5"],
"tags_shipped": ["v0.11.0","v0.11.1","v0.11.2","v0.11.3","v0.11.4","v0.11.5"],
"wave": "C (P11 SVID chain, P12 backup symlink) next",
"phases_shipped": ["P0","P1","P2","P3","P4","P5","P6","P7","P8","P9","P10"],
"tags_shipped": ["v0.11.0","v0.11.1","v0.11.2","v0.11.3","v0.11.4","v0.11.5","v0.11.6","v0.11.7","v0.11.8","v0.11.9","v0.11.10"],
"binding_conditions": ["C-29","C-30","C-31","C-32","C-33","C-34","C-35","C-36","C-37","C-38"],
"phase_count": 29,
"load_bearing_rule": "R-021"
+20
View File
@@ -310,6 +310,26 @@ func Restore(opts RestoreOptions) error {
}
continue
case tar.TypeSymlink:
// REQ-127 / F7: validate Linkname to prevent symlink attacks.
// Reject absolute links, .. traversal, and links outside
// the target dir (which could point to /etc/shadow etc.).
link := hdr.Linkname
if link == "" {
return fmt.Errorf("restore: empty symlink linkname for %q", name)
}
if strings.HasPrefix(link, "/") {
return fmt.Errorf("restore: symlink %q has absolute linkname %q (REQ-127: path traversal)", name, link)
}
if strings.Contains(link, "..") {
// Resolve the link relative to the dest dir; if it
// escapes the target, reject.
linkDest := filepath.Join(filepath.Dir(dest), link)
linkClean := filepath.Clean(linkDest)
targetClean := filepath.Clean(target)
if !strings.HasPrefix(linkClean, targetClean+string(filepath.Separator)) && linkClean != targetClean {
return fmt.Errorf("restore: symlink %q linkname %q escapes target (REQ-127)", name, link)
}
}
if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("restore: clear symlink %s: %w", name, err)
}
+97
View File
@@ -1,7 +1,11 @@
package backup
import (
"archive/tar"
"bytes"
"compress/gzip"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"os"
@@ -314,3 +318,96 @@ func TestBackupSignatureFileContent(t *testing.T) {
func hexDecode(s string) ([]byte, error) {
return hex.DecodeString(s)
}
// --- REQ-127 / F7 backup symlink validation tests ---
// TestRestoreRejectsAbsoluteSymlink verifies a tarball with an absolute
// symlink linkname is rejected.
func TestRestoreRejectsAbsoluteSymlink(t *testing.T) {
dir := t.TempDir()
// Create a crafted tarball with an absolute symlink.
tarPath := filepath.Join(dir, "evil.tar.gz")
sigPath := tarPath + ".sig"
if err := createCraftedTarball(tarPath, "link", "/etc/shadow"); err != nil {
t.Fatalf("create tarball: %v", err)
}
// Create a valid signature (the signature verifies, but the symlink
// validation should still reject the restore).
key := make([]byte, 32)
for i := range key {
key[i] = byte(i)
}
mac := hmac.New(sha256.New, key)
data, _ := os.ReadFile(tarPath)
mac.Write(data)
if err := os.WriteFile(sigPath, []byte(hex.EncodeToString(mac.Sum(nil))), 0o600); err != nil {
t.Fatalf("write sig: %v", err)
}
target := filepath.Join(dir, "restore")
os.MkdirAll(target, 0o755)
err := Restore(RestoreOptions{
InputPath: tarPath,
TargetDir: target,
MasterKey: key,
Force: true,
})
if err == nil {
t.Fatal("Restore should reject absolute symlink (REQ-127)")
}
if !strings.Contains(err.Error(), "absolute") {
t.Errorf("error should mention absolute: %v", err)
}
}
// TestRestoreRejectsTraversalSymlink verifies a tarball with a .. symlink
// that escapes the target is rejected.
func TestRestoreRejectsTraversalSymlink(t *testing.T) {
dir := t.TempDir()
tarPath := filepath.Join(dir, "evil2.tar.gz")
sigPath := tarPath + ".sig"
if err := createCraftedTarball(tarPath, "link", "../../etc/shadow"); err != nil {
t.Fatalf("create tarball: %v", err)
}
key := make([]byte, 32)
for i := range key {
key[i] = byte(i + 1)
}
mac := hmac.New(sha256.New, key)
data, _ := os.ReadFile(tarPath)
mac.Write(data)
if err := os.WriteFile(sigPath, []byte(hex.EncodeToString(mac.Sum(nil))), 0o600); err != nil {
t.Fatalf("write sig: %v", err)
}
target := filepath.Join(dir, "restore2")
os.MkdirAll(target, 0o755)
err := Restore(RestoreOptions{
InputPath: tarPath,
TargetDir: target,
MasterKey: key,
Force: true,
})
if err == nil {
t.Fatal("Restore should reject traversal symlink (REQ-127)")
}
}
// createCraftedTarball creates a tar.gz containing a single symlink
// entry with the given linkname. Used to test symlink validation.
func createCraftedTarball(path, name, linkname string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
gz := gzip.NewWriter(f)
defer gz.Close()
tw := tar.NewWriter(gz)
defer tw.Close()
hdr := &tar.Header{
Name: name,
Typeflag: tar.TypeSymlink,
Linkname: linkname,
Mode: 0o644,
}
return tw.WriteHeader(hdr)
}
+107
View File
@@ -276,11 +276,118 @@ var secretsDeleteCmd = &cobra.Command{
},
}
var secretsRotateMasterDryRun bool
var secretsRotateMasterCmd = &cobra.Command{
Use: "rotate-master",
Short: "Generate a new master key + re-encrypt all namespace secrets (REQ-129, C-30)",
Long: `Generate a new master key, re-encrypt every namespace's .env.secrets
under the new key, and re-seal the master key to OIDC. With --dry-run,
reports the affected namespaces without writing. Atomic per-namespace;
automatic rollback to the old key on any failure (C-30).`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
mkPath := paths.MasterKeyPath()
oldKey, err := secrets.LoadMasterKey(mkPath)
if err != nil {
return fmt.Errorf("load current master key: %w", err)
}
// Find all namespaces with .env.secrets files.
root := paths.Root()
entries, err := os.ReadDir(root)
if err != nil {
return fmt.Errorf("read ORCA_HOME: %w", err)
}
var namespaces []string
for _, ent := range entries {
if !ent.IsDir() || ent.Name() == "cluster" {
continue
}
secPath := paths.NSSecrets(ent.Name())
if _, err := os.Stat(secPath); err == nil {
namespaces = append(namespaces, ent.Name())
}
}
if secretsRotateMasterDryRun {
fmt.Fprintf(cmd.OutOrStdout(), "dry-run: would re-encrypt %d namespace(s) under a new master key:\n", len(namespaces))
for _, ns := range namespaces {
fmt.Fprintf(cmd.OutOrStdout(), " - %s\n", ns)
}
return nil
}
// Generate new master key.
newKey, err := secrets.GenerateMasterKey()
if err != nil {
return fmt.Errorf("generate new master key: %w", err)
}
// Re-encrypt each namespace. On any failure, rollback.
rolled := make(map[string][]string) // ns -> old encrypted (for rollback)
for _, ns := range namespaces {
_, lines, err := loadMasterAndNSSecrets(ns)
if err != nil {
// Rollback already-processed namespaces.
rollbackRotation(rolled, oldKey)
return fmt.Errorf("load secrets for ns %s: %w", ns, err)
}
// Save the old encrypted content for rollback.
secPath := paths.NSSecrets(ns)
oldEnc, _ := os.ReadFile(secPath)
rolled[ns] = []string{string(oldEnc)}
// Re-encrypt under the new key.
newNSKey, err := secrets.DeriveNamespaceKey(newKey, ns)
if err != nil {
rollbackRotation(rolled, oldKey)
return fmt.Errorf("derive new ns key for %s: %w", ns, err)
}
enc, err := secrets.EncryptEnvFile(newNSKey, lines)
if err != nil {
rollbackRotation(rolled, oldKey)
return fmt.Errorf("re-encrypt ns %s: %w", ns, err)
}
if err := writeAtomicFile(secPath, []byte(enc), 0o600); err != nil {
rollbackRotation(rolled, oldKey)
return fmt.Errorf("write ns %s: %w", ns, err)
}
}
// Save the new master key.
if err := secrets.SaveMasterKey(mkPath, newKey); err != nil {
rollbackRotation(rolled, oldKey)
return fmt.Errorf("save new master key (rolled back): %w", err)
}
slog.Info("secrets rotate-master", "namespaces", len(namespaces))
if jsonOutput {
return printJSON(map[string]any{"rotated": true, "namespaces": namespaces})
}
fmt.Fprintf(cmd.OutOrStdout(), "✓ Master key rotated; %d namespace(s) re-encrypted\n", len(namespaces))
return nil
},
}
// rollbackRotation restores old encrypted secrets for already-processed
// namespaces (C-30: automatic rollback on failure).
func rollbackRotation(rolled map[string][]string, oldKey []byte) {
mkPath := paths.MasterKeyPath()
_ = secrets.SaveMasterKey(mkPath, oldKey) // restore old key
for ns, oldEnc := range rolled {
if len(oldEnc) > 0 {
_ = writeAtomicFile(paths.NSSecrets(ns), []byte(oldEnc[0]), 0o600)
}
}
}
func init() {
secretsCmd.AddCommand(secretsSetCmd)
secretsCmd.AddCommand(secretsGetCmd)
secretsCmd.AddCommand(secretsListCmd)
secretsCmd.AddCommand(secretsRotateCmd)
secretsCmd.AddCommand(secretsDeleteCmd)
secretsRotateMasterCmd.Flags().BoolVar(&secretsRotateMasterDryRun, "dry-run", false, "report affected namespaces without writing (C-30)")
secretsCmd.AddCommand(secretsRotateMasterCmd)
rootCmd.AddCommand(secretsCmd)
}
+43 -2
View File
@@ -37,8 +37,8 @@ func MintSVID(ctx context.Context, transport execer, leadPeer, namespace, sa, al
return nil, nil, errors.New("identity: lead peer not set")
}
spiffeID := SpiffeURI(namespace, sa, allocID)
certOut := "/tmp/orca-svid-" + sanitize(spiffeID) + ".crt"
keyOut := "/tmp/orca-svid-" + sanitize(spiffeID) + ".key"
certOut := "/etc/orca/step-tmp/orca-svid-" + sanitize(spiffeID) + ".crt"
keyOut := "/etc/orca/step-tmp/orca-svid-" + sanitize(spiffeID) + ".key"
var sb strings.Builder
sb.WriteString("step ca certificate ")
sb.WriteString(shellQuote(spiffeID))
@@ -99,6 +99,47 @@ func VerifySVID(certPEM []byte, spiffeID string) error {
return fmt.Errorf("identity: cert missing %q: %w", spiffeID, ErrSpiffeURIMissing)
}
// VerifySVIDWithChain validates the SVID cert chain against the CA
// pool AND checks the SPIFFE URI SAN (REQ-126, F9). The CA pool is the
// cluster root CA (or the step-ca root). Rejects certs signed by
// unknown CAs even with a correct URI. This is the hardened
// verification path; VerifySVID (above) only checks the URI and is
// retained for backward compatibility (callers that have already
// verified the chain via mTLS).
func VerifySVIDWithChain(certPEM []byte, spiffeID string, caPool *x509.CertPool) error {
if caPool == nil {
return fmt.Errorf("identity: VerifySVIDWithChain requires a non-nil CA pool (REQ-126)")
}
block, _ := pem.Decode(certPEM)
if block == nil {
return fmt.Errorf("identity: parse cert: PEM decode failed: %w", ErrStepCLI)
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return fmt.Errorf("identity: parse cert: %w", err)
}
// Verify the cert chain against the CA pool.
if _, err := cert.Verify(x509.VerifyOptions{
Roots: caPool,
// SVIDs are client certs (workload identity); they don't have
// EKU for serverAuth, so we use the default (any EKU).
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}); err != nil {
return fmt.Errorf("identity: SVID chain validation failed: %w (REQ-126: unknown CA or expired)", err)
}
// Check the SPIFFE URI SAN.
want, err := url.Parse(spiffeID)
if err != nil {
return fmt.Errorf("identity: parse spiffe id: %w", err)
}
for _, u := range cert.URIs {
if u.String() == want.String() {
return nil
}
}
return fmt.Errorf("identity: cert missing %q: %w", spiffeID, ErrSpiffeURIMissing)
}
func SpiffeIDFromCert(cert *x509.Certificate) string {
for _, u := range cert.URIs {
if u.Scheme == "spiffe" {
+30 -6
View File
@@ -155,8 +155,8 @@ func TestMintSVID_Success(t *testing.T) {
keyPEM := []byte("-----BEGIN PRIVATE KEY-----\nFAKE\n-----END PRIVATE KEY-----\n")
mx := &mockExec{responses: []mockResp{
{match: "step ca certificate", out: nil, err: nil},
{match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: certPEM, err: nil},
{match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: keyPEM, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: certPEM, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: keyPEM, err: nil},
{match: "rm -f", out: nil, err: nil},
}}
gotCert, gotKey, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123")
@@ -203,8 +203,8 @@ func TestMintSVID_EmptyLead(t *testing.T) {
func TestMintSVID_EmptyCert(t *testing.T) {
mx := &mockExec{responses: []mockResp{
{match: "step ca certificate", out: nil, err: nil},
{match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: nil, err: nil},
{match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: []byte("KEY"), err: nil},
{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: nil, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: []byte("KEY"), err: nil},
{match: "rm -f", out: nil, err: nil},
}}
_, _, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123")
@@ -217,8 +217,8 @@ func TestMintSVID_URISANMissing(t *testing.T) {
wrongCert := mintTestSVIDCert(t, "spiffe://orca.local/ns/other/sa/api/0")
mx := &mockExec{responses: []mockResp{
{match: "step ca certificate", out: nil, err: nil},
{match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: wrongCert, err: nil},
{match: "cat '/tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: []byte("KEY"), err: nil},
{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.crt'", out: wrongCert, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-svid-spiffe-orca.local_ns__defaults_sa_web_abc123.key'", out: []byte("KEY"), err: nil},
{match: "rm -f", out: nil, err: nil},
}}
_, _, err := MintSVID(context.Background(), mx, "lead:22", "_defaults", "web", "abc123")
@@ -240,3 +240,27 @@ func TestSanitize(t *testing.T) {
t.Errorf("sanitize = %q, want %q", got, want)
}
}
// --- REQ-126 / F9 SVID chain validation tests ---
// TestVerifySVIDWithChain_RejectsUnknownCA verifies a cert from a
// wrong CA is rejected.
func TestVerifySVIDWithChain_RejectsUnknownCA(t *testing.T) {
// Generate a cert signed by a different CA (not the pool's CA).
certPEM := mintTestSVIDCert(t, "spiffe://orca.local/ns/test/sa/web/alloc-1")
// Empty CA pool (no trusted roots).
emptyPool := x509.NewCertPool()
err := VerifySVIDWithChain(certPEM, "spiffe://orca.local/ns/test/sa/web/alloc-1", emptyPool)
if err == nil {
t.Error("VerifySVIDWithChain should reject cert from unknown CA (REQ-126)")
}
}
// TestVerifySVIDWithChain_NilPoolRejected verifies nil CA pool errors.
func TestVerifySVIDWithChain_NilPoolRejected(t *testing.T) {
certPEM := mintTestSVIDCert(t, "spiffe://orca.local/ns/test/sa/web/alloc-1")
err := VerifySVIDWithChain(certPEM, "spiffe://orca.local/ns/test/sa/web/alloc-1", nil)
if err == nil {
t.Error("nil CA pool should error (REQ-126)")
}
}
+9 -4
View File
@@ -141,8 +141,8 @@ func (c *Client) IssueSVID(ctx context.Context, spiffeID string, sans []string)
// the duration string passed verbatim to `--not-after`. provisioner,
// when non-empty, is passed as `--provisioner`.
func (c *Client) issueCert(ctx context.Context, subject string, sans []string, notAfter string, provisioner string) (string, string, error) {
certOut := fmt.Sprintf("/tmp/orca-%s.crt", sanitize(subject))
keyOut := fmt.Sprintf("/tmp/orca-%s.key", sanitize(subject))
certOut := fmt.Sprintf("/etc/orca/step-tmp/orca-%s.crt", sanitize(subject))
keyOut := fmt.Sprintf("/etc/orca/step-tmp/orca-%s.key", sanitize(subject))
var sb strings.Builder
sb.WriteString("step ca certificate ")
sb.WriteString(shellQuote(subject))
@@ -164,6 +164,11 @@ func (c *Client) issueCert(ctx context.Context, subject string, sans []string, n
}
sb.WriteString(" --force")
cmd := sb.String()
// REQ-128 / F10: ensure the step-tmp dir exists at 0700 before
// writing certs/keys there (not world-readable /tmp).
if _, err := c.run(ctx, "mkdir -p /etc/orca/step-tmp && chmod 700 /etc/orca/step-tmp"); err != nil {
return "", "", fmt.Errorf("stepca: mkdir step-tmp: %w", err)
}
if _, err := c.run(ctx, cmd); err != nil {
return "", "", fmt.Errorf("stepca: issue %s: %w", subject, err)
}
@@ -190,8 +195,8 @@ func (c *Client) RenewServerCert(ctx context.Context, peer string) error {
if perr := c.preflight(); perr != nil {
return perr
}
certPath := fmt.Sprintf("/tmp/orca-%s.crt", sanitize(peer))
keyPath := fmt.Sprintf("/tmp/orca-%s.key", sanitize(peer))
certPath := fmt.Sprintf("/etc/orca/step-tmp/orca-%s.crt", sanitize(peer))
keyPath := fmt.Sprintf("/etc/orca/step-tmp/orca-%s.key", sanitize(peer))
cmd := fmt.Sprintf("step ca renew %s %s --force", shellQuote(certPath), shellQuote(keyPath))
if _, err := c.run(ctx, cmd); err != nil {
return fmt.Errorf("stepca: renew %s: %w", peer, err)
+9 -9
View File
@@ -166,8 +166,8 @@ func TestIssueServerCert_Success(t *testing.T) {
keyPEM := []byte("SERVER-KEY-PEM")
mx.responses = []mockResp{
{match: "step ca certificate", out: nil, err: nil},
{match: "cat '/tmp/orca-peer1.crt'", out: certPEM, err: nil},
{match: "cat '/tmp/orca-peer1.key'", out: keyPEM, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-peer1.crt'", out: certPEM, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-peer1.key'", out: keyPEM, err: nil},
{match: "rm -f", out: nil, err: nil},
}
gotCert, gotKey, err := c.IssueServerCert(context.Background(), "peer1", []string{"peer1.orca.local", "10.0.0.1"})
@@ -199,8 +199,8 @@ func TestIssueSVID_Success(t *testing.T) {
keyPEM := []byte("SVID-KEY-PEM")
mx.responses = []mockResp{
{match: "step ca certificate", out: nil, err: nil},
{match: "cat '/tmp/orca-spiffe-orca_ns__defaults_job_web_alloc_0.crt'", out: certPEM, err: nil},
{match: "cat '/tmp/orca-spiffe-orca_ns__defaults_job_web_alloc_0.key'", out: keyPEM, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-spiffe-orca_ns__defaults_job_web_alloc_0.crt'", out: certPEM, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-spiffe-orca_ns__defaults_job_web_alloc_0.key'", out: keyPEM, err: nil},
{match: "rm -f", out: nil, err: nil},
}
gotCert, gotKey, err := c.IssueSVID(context.Background(), spiffe, []string{"web.orca.local"})
@@ -232,8 +232,8 @@ func TestIssueServerCert_ReadCertFails(t *testing.T) {
c, mx := newMockClient(t, "lead:22")
mx.responses = []mockResp{
{match: "step ca certificate", out: nil, err: nil},
{match: "cat '/tmp/orca-peer1.crt'", out: nil, err: errors.New("ssh: cat failed")},
{match: "cat '/tmp/orca-peer1.key'", out: nil, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-peer1.crt'", out: nil, err: errors.New("ssh: cat failed")},
{match: "cat '/etc/orca/step-tmp/orca-peer1.key'", out: nil, err: nil},
}
_, _, err := c.IssueServerCert(context.Background(), "peer1", nil)
if err == nil || !strings.Contains(err.Error(), "read") {
@@ -245,8 +245,8 @@ func TestIssueServerCert_EmptyCert(t *testing.T) {
c, mx := newMockClient(t, "lead:22")
mx.responses = []mockResp{
{match: "step ca certificate", out: nil, err: nil},
{match: "cat '/tmp/orca-peer1.crt'", out: nil, err: nil},
{match: "cat '/tmp/orca-peer1.key'", out: []byte("KEY"), err: nil},
{match: "cat '/etc/orca/step-tmp/orca-peer1.crt'", out: nil, err: nil},
{match: "cat '/etc/orca/step-tmp/orca-peer1.key'", out: []byte("KEY"), err: nil},
{match: "rm -f", out: nil, err: nil},
}
_, _, err := c.IssueServerCert(context.Background(), "peer1", nil)
@@ -263,7 +263,7 @@ func TestRenewServerCert_Success(t *testing.T) {
if err := c.RenewServerCert(context.Background(), "peer1"); err != nil {
t.Fatalf("RenewServerCert: %v", err)
}
containsCall(t, mx, "step ca renew '/tmp/orca-peer1.crt' '/tmp/orca-peer1.key' --force")
containsCall(t, mx, "step ca renew '/etc/orca/step-tmp/orca-peer1.crt' '/etc/orca/step-tmp/orca-peer1.key' --force")
}
func TestRenewServerCert_Fails(t *testing.T) {