Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions internal/config/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@
// can't decode columnar objects, and the s3 receiver's destructive drain
// would corrupt the read. See VerifierConfig.
Verifier *VerifierConfig `yaml:"verifier"`

// Rotation, when set, parameterizes the
// director_agent_tls_cert_rotation_correctness driver: which director TLS
// cert/CA rotation to perform mid-run and how the enrolled agent is expected
// to respond. Required by (and only meaningful for) that type. See
// RotationConfig and runDirectorAgentCertRotation.
Rotation *RotationConfig `yaml:"rotation"`
}

// VerifierConfig configures the post-drain DuckDB verifier container (see
Expand Down Expand Up @@ -425,6 +432,65 @@
return "secret"
}

// Rotation parameterizes the director_agent_tls_cert_rotation_correctness
// driver (see TestCase.Rotation, runDirectorAgentCertRotation). The director
// deploys an agent that streams back over the proxy_tls listener; mid-run the
// director's serving cert/CA is rotated on disk and the director is bounced so
// the enrolled agent must re-handshake. Mode selects which rotation and the
// expected agent response.
type RotationConfig struct {

Check failure on line 441 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

other declaration of RotationConfig
// Mode selects the mid-run rotation:
// "same_ca" — re-sign the director leaf under the SAME CA
// (RotateServerCert). The agent reconnects transparently
// because the chain still validates. Verdict: delivery
// resumes (count grows after the bounce).
// "new_ca_recover" — rotate to a BRAND-NEW CA written to ca.crt and served
// at /dl/cert.pem (RotateServerCertNewCA). A bootstrap
// agent (no operator-pinned CA) must re-fetch the new CA
// and reconnect. Verdict: delivery resumes.
// "new_ca_reject" — TWO PHASE. Phase 1 re-signs the leaf under an UNTRUSTED
// CA the director never serves (RotateServerCertWrongCA):
// the agent MUST fail validation, so delivery STALLS
// (a missing stall is a SECURITY failure — validation is
// disabled). Phase 2 restores a trusted leaf
// (RotateServerCert) and delivery must resume.
Mode string `yaml:"mode"`

// SettleSeconds is the pause after a rotation+bounce before the driver samples
// the receiver, giving the director time to rebind its listener and the agent
// time to detect the dropped session and reconnect (default 25s).
SettleSeconds int `yaml:"settle_seconds"`

// StallSeconds (new_ca_reject only) is how long the receiver count must hold
// flat after the untrusted rotation for the bad cert to count as rejected
// (default 20s). The case's endpoint seed loop MUST still be appending fresh
// records during this window, else the stall is vacuous — see the case NOTES.
StallSeconds int `yaml:"stall_seconds"`
}

// Rotation mode values for RotationConfig.Mode.
const (
RotationSameCA = "same_ca"

Check failure on line 473 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

other declaration of RotationSameCA
RotationNewCARecover = "new_ca_recover"

Check failure on line 474 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

other declaration of RotationNewCARecover
RotationNewCAReject = "new_ca_reject"

Check failure on line 475 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

other declaration of RotationNewCAReject
)

// SettleSecondsOrDefault / StallSecondsOrDefault centralize the rotation timing
// defaults so the driver and any caller agree.
func (rc *RotationConfig) SettleSecondsOrDefault() int {
if rc != nil && rc.SettleSeconds > 0 {
return rc.SettleSeconds
}
return 25
}

func (rc *RotationConfig) StallSecondsOrDefault() int {
if rc != nil && rc.StallSeconds > 0 {
return rc.StallSeconds
}
return 20
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// AgentConfig configures an external agent container in the test topology
// (see TestCase.Agent). Unlike endpoints (which the subject connects out to),
// the agent connects INTO the subject over the bench network — it starts after
Expand All @@ -448,6 +514,65 @@
MountsSharedData bool `yaml:"mounts_shared_data"`
}

// Rotation parameterizes the director_agent_tls_cert_rotation_correctness
// driver (see TestCase.Rotation, runDirectorAgentCertRotation). The director
// deploys an agent that streams back over the proxy_tls listener; mid-run the
// director's serving cert/CA is rotated on disk and the director is bounced so
// the enrolled agent must re-handshake. Mode selects which rotation and the
// expected agent response.
type RotationConfig struct {

Check failure on line 523 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

RotationConfig redeclared in this block
// Mode selects the mid-run rotation:
// "same_ca" — re-sign the director leaf under the SAME CA
// (RotateServerCert). The agent reconnects transparently
// because the chain still validates. Verdict: delivery
// resumes (count grows after the bounce).
// "new_ca_recover" — rotate to a BRAND-NEW CA written to ca.crt and served
// at /dl/cert.pem (RotateServerCertNewCA). A bootstrap
// agent (no operator-pinned CA) must re-fetch the new CA
// and reconnect. Verdict: delivery resumes.
// "new_ca_reject" — TWO PHASE. Phase 1 re-signs the leaf under an UNTRUSTED
// CA the director never serves (RotateServerCertWrongCA):
// the agent MUST fail validation, so delivery STALLS
// (a missing stall is a SECURITY failure — validation is
// disabled). Phase 2 restores a trusted leaf
// (RotateServerCert) and delivery must resume.
Mode string `yaml:"mode"`

// SettleSeconds is the pause after a rotation+bounce before the driver samples
// the receiver, giving the director time to rebind its listener and the agent
// time to detect the dropped session and reconnect (default 25s).
SettleSeconds int `yaml:"settle_seconds"`

// StallSeconds (new_ca_reject only) is how long the receiver count must hold
// flat after the untrusted rotation for the bad cert to count as rejected
// (default 20s). The case's endpoint seed loop MUST still be appending fresh
// records during this window, else the stall is vacuous — see the case NOTES.
StallSeconds int `yaml:"stall_seconds"`
}

// Rotation mode values for RotationConfig.Mode.
const (
RotationSameCA = "same_ca"

Check failure on line 555 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

RotationSameCA redeclared in this block
RotationNewCARecover = "new_ca_recover"

Check failure on line 556 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

RotationNewCARecover redeclared in this block
RotationNewCAReject = "new_ca_reject"

Check failure on line 557 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

RotationNewCAReject redeclared in this block
)

// SettleSecondsOrDefault / StallSecondsOrDefault centralize the rotation timing
// defaults so the driver and any caller agree.
func (rc *RotationConfig) SettleSecondsOrDefault() int {

Check failure on line 562 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

method RotationConfig.SettleSecondsOrDefault already declared at internal/config/case.go:480:27
if rc != nil && rc.SettleSeconds > 0 {
return rc.SettleSeconds
}
return 25
}

func (rc *RotationConfig) StallSecondsOrDefault() int {

Check failure on line 569 in internal/config/case.go

View workflow job for this annotation

GitHub Actions / Build & Vet

method RotationConfig.StallSecondsOrDefault already declared at internal/config/case.go:487:27
if rc != nil && rc.StallSeconds > 0 {
return rc.StallSeconds
}
return 20
}

Comment on lines +517 to +575

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Duplicate RotationConfig declaration breaks the build.

This entire block — the RotationConfig type (Line 523), the RotationSameCA/RotationNewCARecover/RotationNewCAReject constants (Lines 555-557), and the SettleSecondsOrDefault/StallSecondsOrDefault methods (Lines 562, 569) — is a verbatim re-declaration of the definitions already present earlier in this file (441-493). Go does not permit redeclaration in the same package block, so internal/config no longer compiles.

Remove this duplicated block (or the earlier copy), keeping only one definition.

🐛 Proposed fix — drop the duplicate block
-// Rotation parameterizes the director_agent_tls_cert_rotation_correctness
-// driver (see TestCase.Rotation, runDirectorAgentCertRotation). The director
-// deploys an agent that streams back over the proxy_tls listener; mid-run the
-// director's serving cert/CA is rotated on disk and the director is bounced so
-// the enrolled agent must re-handshake. Mode selects which rotation and the
-// expected agent response.
-type RotationConfig struct {
-	// Mode selects the mid-run rotation:
-	//   "same_ca"        — re-sign the director leaf under the SAME CA
-	//                      (RotateServerCert). The agent reconnects transparently
-	//                      because the chain still validates. Verdict: delivery
-	//                      resumes (count grows after the bounce).
-	//   "new_ca_recover" — rotate to a BRAND-NEW CA written to ca.crt and served
-	//                      at /dl/cert.pem (RotateServerCertNewCA). A bootstrap
-	//                      agent (no operator-pinned CA) must re-fetch the new CA
-	//                      and reconnect. Verdict: delivery resumes.
-	//   "new_ca_reject"  — TWO PHASE. Phase 1 re-signs the leaf under an UNTRUSTED
-	//                      CA the director never serves (RotateServerCertWrongCA):
-	//                      the agent MUST fail validation, so delivery STALLS
-	//                      (a missing stall is a SECURITY failure — validation is
-	//                      disabled). Phase 2 restores a trusted leaf
-	//                      (RotateServerCert) and delivery must resume.
-	Mode string `yaml:"mode"`
-
-	// SettleSeconds is the pause after a rotation+bounce before the driver samples
-	// the receiver, giving the director time to rebind its listener and the agent
-	// time to detect the dropped session and reconnect (default 25s).
-	SettleSeconds int `yaml:"settle_seconds"`
-
-	// StallSeconds (new_ca_reject only) is how long the receiver count must hold
-	// flat after the untrusted rotation for the bad cert to count as rejected
-	// (default 20s). The case's endpoint seed loop MUST still be appending fresh
-	// records during this window, else the stall is vacuous — see the case NOTES.
-	StallSeconds int `yaml:"stall_seconds"`
-}
-
-// Rotation mode values for RotationConfig.Mode.
-const (
-	RotationSameCA       = "same_ca"
-	RotationNewCARecover = "new_ca_recover"
-	RotationNewCAReject  = "new_ca_reject"
-)
-
-// SettleSecondsOrDefault / StallSecondsOrDefault centralize the rotation timing
-// defaults so the driver and any caller agree.
-func (rc *RotationConfig) SettleSecondsOrDefault() int {
-	if rc != nil && rc.SettleSeconds > 0 {
-		return rc.SettleSeconds
-	}
-	return 25
-}
-
-func (rc *RotationConfig) StallSecondsOrDefault() int {
-	if rc != nil && rc.StallSeconds > 0 {
-		return rc.StallSeconds
-	}
-	return 20
-}
-

Confirm which copy carries the canonical doc comments before deleting; keep that one.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Rotation parameterizes the director_agent_tls_cert_rotation_correctness
// driver (see TestCase.Rotation, runDirectorAgentCertRotation). The director
// deploys an agent that streams back over the proxy_tls listener; mid-run the
// director's serving cert/CA is rotated on disk and the director is bounced so
// the enrolled agent must re-handshake. Mode selects which rotation and the
// expected agent response.
type RotationConfig struct {
// Mode selects the mid-run rotation:
// "same_ca" — re-sign the director leaf under the SAME CA
// (RotateServerCert). The agent reconnects transparently
// because the chain still validates. Verdict: delivery
// resumes (count grows after the bounce).
// "new_ca_recover" — rotate to a BRAND-NEW CA written to ca.crt and served
// at /dl/cert.pem (RotateServerCertNewCA). A bootstrap
// agent (no operator-pinned CA) must re-fetch the new CA
// and reconnect. Verdict: delivery resumes.
// "new_ca_reject" — TWO PHASE. Phase 1 re-signs the leaf under an UNTRUSTED
// CA the director never serves (RotateServerCertWrongCA):
// the agent MUST fail validation, so delivery STALLS
// (a missing stall is a SECURITY failure — validation is
// disabled). Phase 2 restores a trusted leaf
// (RotateServerCert) and delivery must resume.
Mode string `yaml:"mode"`
// SettleSeconds is the pause after a rotation+bounce before the driver samples
// the receiver, giving the director time to rebind its listener and the agent
// time to detect the dropped session and reconnect (default 25s).
SettleSeconds int `yaml:"settle_seconds"`
// StallSeconds (new_ca_reject only) is how long the receiver count must hold
// flat after the untrusted rotation for the bad cert to count as rejected
// (default 20s). The case's endpoint seed loop MUST still be appending fresh
// records during this window, else the stall is vacuous — see the case NOTES.
StallSeconds int `yaml:"stall_seconds"`
}
// Rotation mode values for RotationConfig.Mode.
const (
RotationSameCA = "same_ca"
RotationNewCARecover = "new_ca_recover"
RotationNewCAReject = "new_ca_reject"
)
// SettleSecondsOrDefault / StallSecondsOrDefault centralize the rotation timing
// defaults so the driver and any caller agree.
func (rc *RotationConfig) SettleSecondsOrDefault() int {
if rc != nil && rc.SettleSeconds > 0 {
return rc.SettleSeconds
}
return 25
}
func (rc *RotationConfig) StallSecondsOrDefault() int {
if rc != nil && rc.StallSeconds > 0 {
return rc.StallSeconds
}
return 20
}
🧰 Tools
🪛 GitHub Actions: CI / 2_Build & Vet.txt

[error] 523-523: Go build failed: RotationConfig redeclared in this block.

🪛 GitHub Actions: CI / Build & Vet

[error] 523-523: Go build failed: RotationConfig redeclared in this block

🪛 GitHub Check: Build & Vet

[failure] 569-569:
method RotationConfig.StallSecondsOrDefault already declared at internal/config/case.go:487:27


[failure] 562-562:
method RotationConfig.SettleSecondsOrDefault already declared at internal/config/case.go:480:27


[failure] 557-557:
RotationNewCAReject redeclared in this block


[failure] 556-556:
RotationNewCARecover redeclared in this block


[failure] 555-555:
RotationSameCA redeclared in this block


[failure] 523-523:
RotationConfig redeclared in this block

🪛 golangci-lint (2.12.2)

[error] 523-523: : # github.com/VirtualMetric/PipeBench/internal/config [github.com/VirtualMetric/PipeBench/internal/config.test]
internal/config/case.go:523:6: RotationConfig redeclared in this block
internal/config/case.go:441:6: other declaration of RotationConfig
internal/config/case.go:555:2: RotationSameCA redeclared in this block
internal/config/case.go:473:2: other declaration of RotationSameCA
internal/config/case.go:556:2: RotationNewCARecover redeclared in this block
internal/config/case.go:474:2: other declaration of RotationNewCARecover
internal/config/case.go:557:2: RotationNewCAReject redeclared in this block
internal/config/case.go:475:2: other declaration of RotationNewCAReject
internal/config/case.go:562:27: method RotationConfig.SettleSecondsOrDefault already declared at internal/config/case.go:480:27
internal/config/case.go:569:27: method RotationConfig.StallSecondsOrDefault already declared at internal/config/case.go:487:27
internal/config/case.go:735:21: method TestCase.IsDirectorAgentRotationType already declared at internal/config/case.go:724:21

(typecheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/case.go` around lines 517 - 575, The build is broken by a
duplicate RotationConfig definition in this file. Remove one of the two copies
of the RotationConfig type, the
RotationSameCA/RotationNewCARecover/RotationNewCAReject constants, and the
SettleSecondsOrDefault/StallSecondsOrDefault methods, keeping only the canonical
block with the intended doc comments. Use the existing RotationConfig,
RotationSameCA, and SettleSecondsOrDefault symbols to locate both declarations
and delete the redundant one so internal/config compiles cleanly.

Sources: Linters/SAST tools, Pipeline failures

// Endpoint is an auxiliary container in the test topology (see
// TestCase.Endpoints). It's a host the subject reaches on the bench network —
// not a generator or receiver.
Expand Down Expand Up @@ -593,10 +718,24 @@
// DuckDB verifier container instead of a receiver.
func (tc *TestCase) UsesVerifier() bool { return tc.Verifier != nil }

// IsDirectorAgentRotationType reports whether the case is the director↔agent
// TLS cert-rotation correctness flow, which has its own subject-driven (no
// generator) driver — see runDirectorAgentCertRotation.
func (tc *TestCase) IsDirectorAgentRotationType() bool {
return tc.Type == "director_agent_tls_cert_rotation_correctness"
}

// UsesAgent reports whether the case adds an external agent container to the
// topology. The agent connects into the subject rather than being connected to.
func (tc *TestCase) UsesAgent() bool { return tc.Agent != nil }

// IsDirectorAgentRotationType reports whether the case is the director↔agent
// TLS cert-rotation correctness flow, which has its own subject-driven (no
// generator) driver — see runDirectorAgentCertRotation.
func (tc *TestCase) IsDirectorAgentRotationType() bool {
return tc.Type == "director_agent_tls_cert_rotation_correctness"
}

Comment on lines +732 to +738

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Duplicate IsDirectorAgentRotationType method.

This method is declared a second time here; the original is already defined earlier (around Line 724). Like the duplicated RotationConfig block above, this redeclaration fails go vet/build (method TestCase.IsDirectorAgentRotationType already declared). Remove one of the two copies.

🐛 Proposed fix — remove the duplicate method
-// IsDirectorAgentRotationType reports whether the case is the director↔agent
-// TLS cert-rotation correctness flow, which has its own subject-driven (no
-// generator) driver — see runDirectorAgentCertRotation.
-func (tc *TestCase) IsDirectorAgentRotationType() bool {
-	return tc.Type == "director_agent_tls_cert_rotation_correctness"
-}
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// IsDirectorAgentRotationType reports whether the case is the director↔agent
// TLS cert-rotation correctness flow, which has its own subject-driven (no
// generator) driver — see runDirectorAgentCertRotation.
func (tc *TestCase) IsDirectorAgentRotationType() bool {
return tc.Type == "director_agent_tls_cert_rotation_correctness"
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/case.go` around lines 732 - 738, The
TestCase.IsDirectorAgentRotationType method is duplicated, causing a
redeclaration build/vet failure. Remove the extra copy of
IsDirectorAgentRotationType in TestCase and keep only the original definition
that already exists earlier in case.go; make sure the remaining method still
returns the director_agent_tls_cert_rotation_correctness type check.

Sources: Linters/SAST tools, Pipeline failures

// IsPerformanceType reports whether the case is scored as a throughput test —
// the plain `performance` type or the Kafka variant `kafka_performance`.
func (tc *TestCase) IsPerformanceType() bool {
Expand Down Expand Up @@ -726,6 +865,53 @@
if err := tc.validateVerifier(); err != nil {
return err
}
if err := tc.validateRotation(); err != nil {
return err
}
return nil
}

// validateRotation checks the optional `rotation:` block and the
// director_agent_tls_cert_rotation_correctness type's structural requirements:
// the block is required for (and only meaningful to) that type, the mode must be
// known, and the case must be subject-driven (an endpoint the director deploys
// onto, no generator) with a min_received floor for the verdict.
func (tc *TestCase) validateRotation() error {
if !tc.IsDirectorAgentRotationType() {
if tc.Rotation != nil {
return fmt.Errorf("case %q: `rotation:` is only valid for type director_agent_tls_cert_rotation_correctness", tc.Name)
}
return nil
}
if tc.Rotation == nil {
return fmt.Errorf("case %q: type director_agent_tls_cert_rotation_correctness requires a `rotation:` block", tc.Name)
}
switch tc.Rotation.Mode {
case RotationSameCA, RotationNewCARecover, RotationNewCAReject:
default:
return fmt.Errorf("case %q: rotation.mode %q must be one of %s, %s, %s",
tc.Name, tc.Rotation.Mode, RotationSameCA, RotationNewCARecover, RotationNewCAReject)
}
// 0/unset defaults via SettleSecondsOrDefault/StallSecondsOrDefault; reject
// negatives so a typo like `settle_seconds: -5` can't be silently defaulted.
if tc.Rotation.SettleSeconds < 0 {
return fmt.Errorf("case %q: rotation.settle_seconds must be >= 0 (0/unset defaults to 25), got %d", tc.Name, tc.Rotation.SettleSeconds)
}
if tc.Rotation.StallSeconds < 0 {
return fmt.Errorf("case %q: rotation.stall_seconds must be >= 0 (0/unset defaults to 20), got %d", tc.Name, tc.Rotation.StallSeconds)
}
// The director drives data by collecting from an endpoint and forwarding —
// there is no generator, so the verdict rests on min_received plus the
// post-rotation count behaviour. Guard the two structural preconditions.
if tc.HasGenerator() {
return fmt.Errorf("case %q: director_agent_tls_cert_rotation_correctness is subject-driven and must not declare a generator", tc.Name)
}
if len(tc.Endpoints) == 0 {
return fmt.Errorf("case %q: director_agent_tls_cert_rotation_correctness requires an `endpoints:` block (the host the director deploys the agent onto)", tc.Name)
}
if tc.Correctness.MinReceived <= 0 {
return fmt.Errorf("case %q: director_agent_tls_cert_rotation_correctness requires correctness.min_received > 0", tc.Name)
}
return nil
}

Expand Down
78 changes: 78 additions & 0 deletions internal/orchestrator/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,84 @@ func RotateServerCertWrongCA(outDir string, serverHosts []string) error {
return writePEMKey(filepath.Join(abs, "server.key"), srvKey, 0o644)
}

// RotateServerCertNewCA rotates the entire trust root: it generates a BRAND-NEW
// CA, overwrites ca.crt / ca.key in place, and re-signs server.crt / server.key
// under the new CA (same SAN set). Unlike RotateServerCertWrongCA — which throws
// the new CA away so nothing can ever trust the new leaf — this PERSISTS the new
// CA, so a party that re-reads ca.crt (e.g. a director serving it at
// /dl/cert.pem, or a bootstrap agent that re-fetches it) can recover trust and
// reconnect. It models a full CA rollover with re-distribution. Used by the
// director↔agent "new_ca_recover" rotation case.
//
// The client bundle (client.crt) is intentionally left untouched: the
// director↔agent cases have no generator/client leaf, and rewriting it would
// only matter to a client that pins the old CA, which this rollover deliberately
// supersedes.
func RotateServerCertNewCA(outDir string, serverHosts []string) error {
abs, err := filepath.Abs(outDir)
if err != nil {
return err
}

// Fresh CA — PERSISTED to ca.crt/ca.key (the difference from
// RotateServerCertWrongCA), so anything that re-reads the CA can recover.
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return fmt.Errorf("generate new ca key: %w", err)
}
caTpl := &x509.Certificate{
SerialNumber: bigSerial(),
Subject: pkix.Name{CommonName: "PipeBench Rotated CA"},
NotBefore: time.Now().Add(-1 * time.Hour),
NotAfter: time.Now().Add(24 * time.Hour),
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
caDER, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, &caKey.PublicKey, caKey)
if err != nil {
return fmt.Errorf("sign new ca: %w", err)
}
caCert, err := x509.ParseCertificate(caDER)
if err != nil {
return fmt.Errorf("parse new ca: %w", err)
}
if err := writePEMCert(filepath.Join(abs, "ca.crt"), caDER); err != nil {
return err
}
if err := writePEMKey(filepath.Join(abs, "ca.key"), caKey, 0o600); err != nil {
return err
}

if len(serverHosts) == 0 {
serverHosts = []string{"subject"}
}
serverDNS, serverIP := splitHosts(serverHosts)
srvKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return fmt.Errorf("generate server key: %w", err)
}
srvTpl := &x509.Certificate{
SerialNumber: bigSerial(),
Subject: pkix.Name{CommonName: serverHosts[0]},
NotBefore: time.Now().Add(-1 * time.Hour),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
DNSNames: serverDNS,
IPAddresses: serverIP,
}
srvDER, err := x509.CreateCertificate(rand.Reader, srvTpl, caCert, &srvKey.PublicKey, caKey)
if err != nil {
return fmt.Errorf("re-sign server cert under new ca: %w", err)
}
if err := writePEMCert(filepath.Join(abs, "server.crt"), srvDER); err != nil {
return err
}
return writePEMKey(filepath.Join(abs, "server.key"), srvKey, 0o644)
}

// loadCA reads and parses the CA cert + key written by GenerateTLSCerts.
func loadCA(dir string) (*x509.Certificate, *ecdsa.PrivateKey, error) {
certPEM, err := os.ReadFile(filepath.Join(dir, "ca.crt"))
Expand Down
Loading