mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:11:29 +00:00
chore(fmt): repo-wide gofmt -w sweep — close drift surfaced by ci-pipeline-cleanup Phase 4
Mechanical reformat. The new 'gofmt drift' CI step (added in
ci-pipeline-cleanup Phase 4, commit 0f205a8) surfaced 111 files
with accumulated gofmt drift across cmd/, internal/, and deploy/test/.
Each file's diff is gofmt-standard: whitespace adjustments, intra-
group import sorting (alphabetical by import path within blank-line-
separated groups), and struct-tag column alignment. No semantic
changes — verified via 'git diff --ignore-all-space' which shows only
the line-position deltas from import reordering.
The gate stays in place after this commit. Going forward it catches
gofmt drift at PR time.
This commit is contained in:
@@ -692,10 +692,10 @@ func TestMakeRequest_InvalidURL(t *testing.T) {
|
|||||||
// TestCertKeyInfo tests extraction of key algorithm and size from certificates.
|
// TestCertKeyInfo tests extraction of key algorithm and size from certificates.
|
||||||
func TestCertKeyInfo(t *testing.T) {
|
func TestCertKeyInfo(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
genKey func() interface{}
|
genKey func() interface{}
|
||||||
expectedAlg string
|
expectedAlg string
|
||||||
minBitSize int
|
minBitSize int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "ECDSA P-256",
|
name: "ECDSA P-256",
|
||||||
@@ -1503,9 +1503,9 @@ func TestValidateHTTPSScheme(t *testing.T) {
|
|||||||
wantErrSub: "plaintext http://",
|
wantErrSub: "plaintext http://",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bare host missing scheme falls through to unsupported",
|
name: "bare host missing scheme falls through to unsupported",
|
||||||
serverURL: "localhost:8443",
|
serverURL: "localhost:8443",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
// url.Parse treats "localhost:8443" as scheme=localhost,
|
// url.Parse treats "localhost:8443" as scheme=localhost,
|
||||||
// opaque=8443 — exercises the default arm (unsupported scheme)
|
// opaque=8443 — exercises the default arm (unsupported scheme)
|
||||||
// rather than the empty-scheme arm. Both are fail-closed, which
|
// rather than the empty-scheme arm. Both are fail-closed, which
|
||||||
|
|||||||
+9
-9
@@ -34,16 +34,16 @@ import (
|
|||||||
"github.com/shankar0123/certctl/internal/connector/target/apache"
|
"github.com/shankar0123/certctl/internal/connector/target/apache"
|
||||||
"github.com/shankar0123/certctl/internal/connector/target/caddy"
|
"github.com/shankar0123/certctl/internal/connector/target/caddy"
|
||||||
"github.com/shankar0123/certctl/internal/connector/target/envoy"
|
"github.com/shankar0123/certctl/internal/connector/target/envoy"
|
||||||
pf "github.com/shankar0123/certctl/internal/connector/target/postfix"
|
|
||||||
sshconn "github.com/shankar0123/certctl/internal/connector/target/ssh"
|
|
||||||
"github.com/shankar0123/certctl/internal/connector/target/f5"
|
"github.com/shankar0123/certctl/internal/connector/target/f5"
|
||||||
jks "github.com/shankar0123/certctl/internal/connector/target/javakeystore"
|
|
||||||
k8s "github.com/shankar0123/certctl/internal/connector/target/k8ssecret"
|
|
||||||
wcs "github.com/shankar0123/certctl/internal/connector/target/wincertstore"
|
|
||||||
"github.com/shankar0123/certctl/internal/connector/target/haproxy"
|
"github.com/shankar0123/certctl/internal/connector/target/haproxy"
|
||||||
"github.com/shankar0123/certctl/internal/connector/target/iis"
|
"github.com/shankar0123/certctl/internal/connector/target/iis"
|
||||||
|
jks "github.com/shankar0123/certctl/internal/connector/target/javakeystore"
|
||||||
|
k8s "github.com/shankar0123/certctl/internal/connector/target/k8ssecret"
|
||||||
"github.com/shankar0123/certctl/internal/connector/target/nginx"
|
"github.com/shankar0123/certctl/internal/connector/target/nginx"
|
||||||
|
pf "github.com/shankar0123/certctl/internal/connector/target/postfix"
|
||||||
|
sshconn "github.com/shankar0123/certctl/internal/connector/target/ssh"
|
||||||
"github.com/shankar0123/certctl/internal/connector/target/traefik"
|
"github.com/shankar0123/certctl/internal/connector/target/traefik"
|
||||||
|
wcs "github.com/shankar0123/certctl/internal/connector/target/wincertstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AgentConfig represents the agent-side configuration.
|
// AgentConfig represents the agent-side configuration.
|
||||||
@@ -80,10 +80,10 @@ type Agent struct {
|
|||||||
client *http.Client
|
client *http.Client
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
heartbeatInterval time.Duration
|
heartbeatInterval time.Duration
|
||||||
pollInterval time.Duration
|
pollInterval time.Duration
|
||||||
discoveryInterval time.Duration
|
discoveryInterval time.Duration
|
||||||
consecutiveFailures int
|
consecutiveFailures int
|
||||||
|
|
||||||
// I-004: terminal retirement signal. retiredSignal is closed exactly once
|
// I-004: terminal retirement signal. retiredSignal is closed exactly once
|
||||||
// (guarded by retiredOnce) when either sendHeartbeat or pollForWork
|
// (guarded by retiredOnce) when either sendHeartbeat or pollForWork
|
||||||
|
|||||||
+9
-9
@@ -75,8 +75,8 @@ func verifyDeployment(
|
|||||||
// calls, issuer connector communication, or any operation that trusts the
|
// calls, issuer connector communication, or any operation that trusts the
|
||||||
// certificate. The verification result compares SHA-256 fingerprints only.
|
// certificate. The verification result compares SHA-256 fingerprints only.
|
||||||
// See TICKET-016 for full security audit rationale.
|
// See TICKET-016 for full security audit rationale.
|
||||||
InsecureSkipVerify: true, //nolint:gosec // verification probe; documented above + docs/tls.md L-001 table
|
InsecureSkipVerify: true, //nolint:gosec // verification probe; documented above + docs/tls.md L-001 table
|
||||||
ServerName: targetHost, // For SNI
|
ServerName: targetHost, // For SNI
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to connect to %s: %w", address, err)
|
return nil, fmt.Errorf("failed to connect to %s: %w", address, err)
|
||||||
@@ -161,11 +161,11 @@ func (a *Agent) reportVerificationResult(
|
|||||||
|
|
||||||
// Build the request payload
|
// Build the request payload
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"target_id": targetID,
|
"target_id": targetID,
|
||||||
"expected_fingerprint": result.ExpectedFingerprint,
|
"expected_fingerprint": result.ExpectedFingerprint,
|
||||||
"actual_fingerprint": result.ActualFingerprint,
|
"actual_fingerprint": result.ActualFingerprint,
|
||||||
"verified": result.Verified,
|
"verified": result.Verified,
|
||||||
"error": result.Error,
|
"error": result.Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := json.Marshal(payload)
|
body, err := json.Marshal(payload)
|
||||||
@@ -247,7 +247,7 @@ func (a *Agent) verifyAndReportDeployment(
|
|||||||
) {
|
) {
|
||||||
// Perform verification with configured timeout and delay
|
// Perform verification with configured timeout and delay
|
||||||
result, err := verifyDeployment(ctx, targetHost, targetPort, certPEM,
|
result, err := verifyDeployment(ctx, targetHost, targetPort, certPEM,
|
||||||
2*time.Second, // delay before probing
|
2*time.Second, // delay before probing
|
||||||
10*time.Second, // timeout for TLS connection
|
10*time.Second, // timeout for TLS connection
|
||||||
a.logger)
|
a.logger)
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ func (a *Agent) verifyAndReportDeployment(
|
|||||||
}
|
}
|
||||||
// Probe failure: report error but continue
|
// Probe failure: report error but continue
|
||||||
result = &VerificationResult{
|
result = &VerificationResult{
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
VerifiedAt: time.Now().UTC(),
|
VerifiedAt: time.Now().UTC(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,9 +114,9 @@ func TestExtractTargetHostAndPort_InvalidJSON(t *testing.T) {
|
|||||||
|
|
||||||
func TestExtractTargetHostAndPort_AlternativeFieldNames(t *testing.T) {
|
func TestExtractTargetHostAndPort_AlternativeFieldNames(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
config map[string]interface{}
|
config map[string]interface{}
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"host", map[string]interface{}{"host": "host1.com"}, "host1.com"},
|
{"host", map[string]interface{}{"host": "host1.com"}, "host1.com"},
|
||||||
{"hostname", map[string]interface{}{"hostname": "host2.com"}, "host2.com"},
|
{"hostname", map[string]interface{}{"hostname": "host2.com"}, "host2.com"},
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ func TestValidateHTTPSScheme(t *testing.T) {
|
|||||||
wantErrSub: "plaintext http://",
|
wantErrSub: "plaintext http://",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bare host missing scheme rejected",
|
name: "bare host missing scheme rejected",
|
||||||
serverURL: "localhost:8443",
|
serverURL: "localhost:8443",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
// url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443
|
// url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443
|
||||||
// — exercises the default arm (unsupported scheme) rather than the
|
// — exercises the default arm (unsupported scheme) rather than the
|
||||||
// empty-scheme arm. Both are fail-closed, which is what we care about.
|
// empty-scheme arm. Both are fail-closed, which is what we care about.
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ func TestValidateHTTPSScheme(t *testing.T) {
|
|||||||
wantErrSub: "plaintext http://",
|
wantErrSub: "plaintext http://",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bare host missing scheme rejected",
|
name: "bare host missing scheme rejected",
|
||||||
serverURL: "localhost:8443",
|
serverURL: "localhost:8443",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
// url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443
|
// url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443
|
||||||
// — exercises the default arm (unsupported scheme) rather than the
|
// — exercises the default arm (unsupported scheme) rather than the
|
||||||
// empty-scheme arm. Both are fail-closed, which is what we care about.
|
// empty-scheme arm. Both are fail-closed, which is what we care about.
|
||||||
|
|||||||
+14
-14
@@ -149,10 +149,10 @@ func (c *qaClient) do(method, path string, body string) (*http.Response, error)
|
|||||||
return c.http.Do(req)
|
return c.http.Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *qaClient) get(path string) (*http.Response, error) { return c.do("GET", path, "") }
|
func (c *qaClient) get(path string) (*http.Response, error) { return c.do("GET", path, "") }
|
||||||
func (c *qaClient) post(path, body string) (*http.Response, error) { return c.do("POST", path, body) }
|
func (c *qaClient) post(path, body string) (*http.Response, error) { return c.do("POST", path, body) }
|
||||||
func (c *qaClient) put(path, body string) (*http.Response, error) { return c.do("PUT", path, body) }
|
func (c *qaClient) put(path, body string) (*http.Response, error) { return c.do("PUT", path, body) }
|
||||||
func (c *qaClient) delete(path string) (*http.Response, error) { return c.do("DELETE", path, "") }
|
func (c *qaClient) delete(path string) (*http.Response, error) { return c.do("DELETE", path, "") }
|
||||||
|
|
||||||
// statusCode makes a request and returns the HTTP status code.
|
// statusCode makes a request and returns the HTTP status code.
|
||||||
func (c *qaClient) statusCode(method, path, body string) (int, error) {
|
func (c *qaClient) statusCode(method, path, body string) (int, error) {
|
||||||
@@ -228,11 +228,11 @@ type qaCert struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type qaJob struct {
|
type qaJob struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
CertificateID string `json:"certificate_id"`
|
CertificateID string `json:"certificate_id"`
|
||||||
AgentID *string `json:"agent_id"`
|
AgentID *string `json:"agent_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type qaIssuer struct {
|
type qaIssuer struct {
|
||||||
@@ -261,15 +261,15 @@ type qaAgent struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type qaNotification struct {
|
type qaNotification struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Read bool `json:"read"`
|
Read bool `json:"read"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type qaStats struct {
|
type qaStats struct {
|
||||||
TotalCertificates int `json:"total_certificates"`
|
TotalCertificates int `json:"total_certificates"`
|
||||||
ActiveCertificates int `json:"active_certificates"`
|
ActiveCertificates int `json:"active_certificates"`
|
||||||
ExpiringCertificates int `json:"expiring_certificates"`
|
ExpiringCertificates int `json:"expiring_certificates"`
|
||||||
TotalAgents int `json:"total_agents"`
|
TotalAgents int `json:"total_agents"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type qaMetrics struct {
|
type qaMetrics struct {
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ func adversarialPathInputs() []struct {
|
|||||||
{"null_byte_trailing", "mc-001\x00"},
|
{"null_byte_trailing", "mc-001\x00"},
|
||||||
{"null_byte_embedded", "mc-\x00-001"},
|
{"null_byte_embedded", "mc-\x00-001"},
|
||||||
{"long_id_10k", strings.Repeat("A", 10000)},
|
{"long_id_10k", strings.Repeat("A", 10000)},
|
||||||
{"unicode_homoglyph_hyphen", "mc\u2010001"}, // U+2010 HYPHEN
|
{"unicode_homoglyph_hyphen", "mc\u2010001"}, // U+2010 HYPHEN
|
||||||
{"unicode_homoglyph_fullwidth", "mc\uFF0D001"}, // U+FF0D FULLWIDTH HYPHEN-MINUS
|
{"unicode_homoglyph_fullwidth", "mc\uFF0D001"}, // U+FF0D FULLWIDTH HYPHEN-MINUS
|
||||||
{"control_char_newline", "mc-001\n"},
|
{"control_char_newline", "mc-001\n"},
|
||||||
{"control_char_tab", "mc\t001"},
|
{"control_char_tab", "mc\t001"},
|
||||||
{"control_char_bell", "mc\x07001"},
|
{"control_char_bell", "mc\x07001"},
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"errors"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ type MockAgentService struct {
|
|||||||
// I-004: soft-retirement hooks. Tests that don't set these receive nil
|
// I-004: soft-retirement hooks. Tests that don't set these receive nil
|
||||||
// results and nil errors, which mirrors the safest default (no-op) for
|
// results and nil errors, which mirrors the safest default (no-op) for
|
||||||
// unrelated suites that mock only the legacy surface.
|
// unrelated suites that mock only the legacy surface.
|
||||||
RetireAgentFn func(agentID, actor string, force bool, reason string) (*service.AgentRetirementResult, error)
|
RetireAgentFn func(agentID, actor string, force bool, reason string) (*service.AgentRetirementResult, error)
|
||||||
ListRetiredAgentsFn func(page, perPage int) ([]domain.Agent, int64, error)
|
ListRetiredAgentsFn func(page, perPage int) ([]domain.Agent, int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockAgentService) ListAgents(_ context.Context, page, perPage int) ([]domain.Agent, int64, error) {
|
func (m *MockAgentService) ListAgents(_ context.Context, page, perPage int) ([]domain.Agent, int64, error) {
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ func TestRetireAgentHandler_Success_200(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body struct {
|
var body struct {
|
||||||
RetiredAt time.Time `json:"retired_at"`
|
RetiredAt time.Time `json:"retired_at"`
|
||||||
AlreadyRetired bool `json:"already_retired"`
|
AlreadyRetired bool `json:"already_retired"`
|
||||||
Cascade bool `json:"cascade"`
|
Cascade bool `json:"cascade"`
|
||||||
Counts domain.AgentDependencyCounts `json:"counts"`
|
Counts domain.AgentDependencyCounts `json:"counts"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
|
||||||
t.Fatalf("decode 200 body: %v", err)
|
t.Fatalf("decode 200 body: %v", err)
|
||||||
@@ -273,10 +273,10 @@ func TestRetireAgentHandler_ForceCascade_200(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body struct {
|
var body struct {
|
||||||
RetiredAt time.Time `json:"retired_at"`
|
RetiredAt time.Time `json:"retired_at"`
|
||||||
AlreadyRetired bool `json:"already_retired"`
|
AlreadyRetired bool `json:"already_retired"`
|
||||||
Cascade bool `json:"cascade"`
|
Cascade bool `json:"cascade"`
|
||||||
Counts domain.AgentDependencyCounts `json:"counts"`
|
Counts domain.AgentDependencyCounts `json:"counts"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
|
||||||
t.Fatalf("decode force-cascade 200 body: %v", err)
|
t.Fatalf("decode force-cascade 200 body: %v", err)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/shankar0123/certctl/internal/domain"
|
|
||||||
"github.com/shankar0123/certctl/internal/api/middleware"
|
"github.com/shankar0123/certctl/internal/api/middleware"
|
||||||
|
"github.com/shankar0123/certctl/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mockAuditService implements AuditService for testing.
|
// mockAuditService implements AuditService for testing.
|
||||||
type mockAuditService struct {
|
type mockAuditService struct {
|
||||||
listFunc func(page, perPage int) ([]domain.AuditEvent, int64, error)
|
listFunc func(page, perPage int) ([]domain.AuditEvent, int64, error)
|
||||||
getFunc func(id string) (*domain.AuditEvent, error)
|
getFunc func(id string) (*domain.AuditEvent, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockAuditService) ListAuditEvents(_ context.Context, page, perPage int) ([]domain.AuditEvent, int64, error) {
|
func (m *mockAuditService) ListAuditEvents(_ context.Context, page, perPage int) ([]domain.AuditEvent, int64, error) {
|
||||||
|
|||||||
@@ -86,10 +86,10 @@ func TestBulkRenew_PartialFailure_ReportsBoth(t *testing.T) {
|
|||||||
svc := &mockBulkRenewalService{
|
svc := &mockBulkRenewalService{
|
||||||
BulkRenewFn: func(ctx context.Context, criteria domain.BulkRenewalCriteria, actor string) (*domain.BulkRenewalResult, error) {
|
BulkRenewFn: func(ctx context.Context, criteria domain.BulkRenewalCriteria, actor string) (*domain.BulkRenewalResult, error) {
|
||||||
return &domain.BulkRenewalResult{
|
return &domain.BulkRenewalResult{
|
||||||
TotalMatched: 3,
|
TotalMatched: 3,
|
||||||
TotalEnqueued: 2,
|
TotalEnqueued: 2,
|
||||||
TotalSkipped: 0,
|
TotalSkipped: 0,
|
||||||
TotalFailed: 1,
|
TotalFailed: 1,
|
||||||
Errors: []domain.BulkOperationError{
|
Errors: []domain.BulkOperationError{
|
||||||
{CertificateID: "mc-failed", Error: "renewal job enqueue failed: db timeout"},
|
{CertificateID: "mc-failed", Error: "renewal job enqueue failed: db timeout"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -98,12 +98,12 @@ func generateTestCertPEM(t *testing.T) string {
|
|||||||
t.Fatalf("failed to generate key: %v", err)
|
t.Fatalf("failed to generate key: %v", err)
|
||||||
}
|
}
|
||||||
template := &x509.Certificate{
|
template := &x509.Certificate{
|
||||||
SerialNumber: big.NewInt(1),
|
SerialNumber: big.NewInt(1),
|
||||||
Subject: pkix.Name{CommonName: "Test CA"},
|
Subject: pkix.Name{CommonName: "Test CA"},
|
||||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||||
NotAfter: time.Now().Add(24 * time.Hour),
|
NotAfter: time.Now().Add(24 * time.Hour),
|
||||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||||
IsCA: true,
|
IsCA: true,
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
}
|
}
|
||||||
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"errors"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|||||||
@@ -59,12 +59,12 @@ func (h *HealthCheckHandler) ListHealthChecks(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
filter := &repository.HealthCheckFilter{
|
filter := &repository.HealthCheckFilter{
|
||||||
Status: status,
|
Status: status,
|
||||||
CertificateID: certificateID,
|
CertificateID: certificateID,
|
||||||
NetworkScanTargetID: networkScanTargetID,
|
NetworkScanTargetID: networkScanTargetID,
|
||||||
Enabled: enabledFilter,
|
Enabled: enabledFilter,
|
||||||
Page: page,
|
Page: page,
|
||||||
PerPage: perPage,
|
PerPage: perPage,
|
||||||
}
|
}
|
||||||
|
|
||||||
checks, total, err := h.service.List(r.Context(), filter)
|
checks, total, err := h.service.List(r.Context(), filter)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"errors"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"errors"
|
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"errors"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|||||||
@@ -237,9 +237,9 @@ func TestCreateProfile_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"name": "New Profile",
|
"name": "New Profile",
|
||||||
"max_ttl_seconds": 86400,
|
"max_ttl_seconds": 86400,
|
||||||
"allowed_ekus": []string{"serverAuth"},
|
"allowed_ekus": []string{"serverAuth"},
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(body)
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ func TestUpdateProfile_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"name": "Updated Profile",
|
"name": "Updated Profile",
|
||||||
"max_ttl_seconds": 172800,
|
"max_ttl_seconds": 172800,
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(body)
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"errors"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ import (
|
|||||||
|
|
||||||
// MockRenewalPolicyService is a mock implementation of RenewalPolicyService.
|
// MockRenewalPolicyService is a mock implementation of RenewalPolicyService.
|
||||||
type MockRenewalPolicyService struct {
|
type MockRenewalPolicyService struct {
|
||||||
ListRenewalPoliciesFn func(page, perPage int) ([]domain.RenewalPolicy, int64, error)
|
ListRenewalPoliciesFn func(page, perPage int) ([]domain.RenewalPolicy, int64, error)
|
||||||
GetRenewalPolicyFn func(id string) (*domain.RenewalPolicy, error)
|
GetRenewalPolicyFn func(id string) (*domain.RenewalPolicy, error)
|
||||||
CreateRenewalPolicyFn func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
|
CreateRenewalPolicyFn func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
|
||||||
UpdateRenewalPolicyFn func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
|
UpdateRenewalPolicyFn func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
|
||||||
DeleteRenewalPolicyFn func(id string) error
|
DeleteRenewalPolicyFn func(id string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockRenewalPolicyService) ListRenewalPolicies(_ context.Context, page, perPage int) ([]domain.RenewalPolicy, int64, error) {
|
func (m *MockRenewalPolicyService) ListRenewalPolicies(_ context.Context, page, perPage int) ([]domain.RenewalPolicy, int64, error) {
|
||||||
@@ -199,11 +199,11 @@ func TestCreateRenewalPolicy_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"name": "New Policy",
|
"name": "New Policy",
|
||||||
"renewal_window_days": 30,
|
"renewal_window_days": 30,
|
||||||
"max_retries": 3,
|
"max_retries": 3,
|
||||||
"retry_interval_seconds": 3600,
|
"retry_interval_seconds": 3600,
|
||||||
"auto_renew": true,
|
"auto_renew": true,
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(body)
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
|
||||||
@@ -221,8 +221,8 @@ func TestCreateRenewalPolicy_Success(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreateRenewalPolicy_MissingName(t *testing.T) {
|
func TestCreateRenewalPolicy_MissingName(t *testing.T) {
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"renewal_window_days": 30,
|
"renewal_window_days": 30,
|
||||||
"max_retries": 3,
|
"max_retries": 3,
|
||||||
"retry_interval_seconds": 3600,
|
"retry_interval_seconds": 3600,
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(body)
|
bodyBytes, _ := json.Marshal(body)
|
||||||
@@ -261,9 +261,9 @@ func TestCreateRenewalPolicy_DuplicateName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"name": "Duplicate",
|
"name": "Duplicate",
|
||||||
"renewal_window_days": 30,
|
"renewal_window_days": 30,
|
||||||
"max_retries": 3,
|
"max_retries": 3,
|
||||||
"retry_interval_seconds": 3600,
|
"retry_interval_seconds": 3600,
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(body)
|
bodyBytes, _ := json.Marshal(body)
|
||||||
@@ -308,11 +308,11 @@ func TestUpdateRenewalPolicy_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"name": "Updated Policy",
|
"name": "Updated Policy",
|
||||||
"renewal_window_days": 45,
|
"renewal_window_days": 45,
|
||||||
"max_retries": 5,
|
"max_retries": 5,
|
||||||
"retry_interval_seconds": 1800,
|
"retry_interval_seconds": 1800,
|
||||||
"auto_renew": true,
|
"auto_renew": true,
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(body)
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
|
||||||
@@ -336,9 +336,9 @@ func TestUpdateRenewalPolicy_NotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"name": "Updated",
|
"name": "Updated",
|
||||||
"renewal_window_days": 30,
|
"renewal_window_days": 30,
|
||||||
"max_retries": 3,
|
"max_retries": 3,
|
||||||
"retry_interval_seconds": 3600,
|
"retry_interval_seconds": 3600,
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(body)
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
|||||||
@@ -346,8 +346,8 @@ func TestCursorPagedResponse_EmptyNextCursor(t *testing.T) {
|
|||||||
|
|
||||||
func TestFilterFields_SingleObject(t *testing.T) {
|
func TestFilterFields_SingleObject(t *testing.T) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"id": "cert-123",
|
"id": "cert-123",
|
||||||
"name": "My Cert",
|
"name": "My Cert",
|
||||||
"expiry": "2025-01-01",
|
"expiry": "2025-01-01",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
}
|
}
|
||||||
@@ -379,8 +379,8 @@ func TestFilterFields_SingleObject(t *testing.T) {
|
|||||||
func TestFilterFields_EmptyFields(t *testing.T) {
|
func TestFilterFields_EmptyFields(t *testing.T) {
|
||||||
// Empty fields list should return data unchanged
|
// Empty fields list should return data unchanged
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"id": "cert-123",
|
"id": "cert-123",
|
||||||
"name": "My Cert",
|
"name": "My Cert",
|
||||||
}
|
}
|
||||||
|
|
||||||
result := filterFields(data, []string{})
|
result := filterFields(data, []string{})
|
||||||
@@ -398,8 +398,8 @@ func TestFilterFields_EmptyFields(t *testing.T) {
|
|||||||
|
|
||||||
func TestFilterFields_NoMatchingFields(t *testing.T) {
|
func TestFilterFields_NoMatchingFields(t *testing.T) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"id": "cert-123",
|
"id": "cert-123",
|
||||||
"name": "My Cert",
|
"name": "My Cert",
|
||||||
}
|
}
|
||||||
|
|
||||||
result := filterFields(data, []string{"nonexistent", "also-not-there"})
|
result := filterFields(data, []string{"nonexistent", "also-not-there"})
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ func TestValidateCSRPEM_InvalidInputs(t *testing.T) {
|
|||||||
// TestValidatePolicyType_ValidTypes tests valid policy types.
|
// TestValidatePolicyType_ValidTypes tests valid policy types.
|
||||||
func TestValidatePolicyType_ValidTypes(t *testing.T) {
|
func TestValidatePolicyType_ValidTypes(t *testing.T) {
|
||||||
validTypes := []struct {
|
validTypes := []struct {
|
||||||
name string
|
name string
|
||||||
ptype interface{}
|
ptype interface{}
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ func (h VerificationHandler) VerifyDeployment(w http.ResponseWriter, r *http.Req
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"job_id": jobID,
|
"job_id": jobID,
|
||||||
"verified": req.Verified,
|
"verified": req.Verified,
|
||||||
"verified_at": result.VerifiedAt,
|
"verified_at": result.VerifiedAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,11 +221,11 @@ func NewAuditServiceAdapter(recordFn func(ctx context.Context, actor string, act
|
|||||||
// RecordAPICall implements AuditRecorder by translating API call data into an audit event.
|
// RecordAPICall implements AuditRecorder by translating API call data into an audit event.
|
||||||
func (a *AuditServiceAdapter) RecordAPICall(ctx context.Context, method, path, actor string, bodyHash string, status int, latencyMs int64) error {
|
func (a *AuditServiceAdapter) RecordAPICall(ctx context.Context, method, path, actor string, bodyHash string, status int, latencyMs int64) error {
|
||||||
details := map[string]interface{}{
|
details := map[string]interface{}{
|
||||||
"method": method,
|
"method": method,
|
||||||
"path": path,
|
"path": path,
|
||||||
"body_hash": bodyHash,
|
"body_hash": bodyHash,
|
||||||
"status": status,
|
"status": status,
|
||||||
"latency_ms": latencyMs,
|
"latency_ms": latencyMs,
|
||||||
}
|
}
|
||||||
|
|
||||||
action := fmt.Sprintf("api_%s", strings.ToLower(method))
|
action := fmt.Sprintf("api_%s", strings.ToLower(method))
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ import (
|
|||||||
|
|
||||||
// mockAuditRecorder captures RecordAPICall invocations for testing.
|
// mockAuditRecorder captures RecordAPICall invocations for testing.
|
||||||
type mockAuditRecorder struct {
|
type mockAuditRecorder struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
calls []auditCall
|
calls []auditCall
|
||||||
err error // if non-nil, RecordAPICall returns this
|
err error // if non-nil, RecordAPICall returns this
|
||||||
block chan struct{} // if non-nil, RecordAPICall blocks on receive before returning
|
block chan struct{} // if non-nil, RecordAPICall blocks on receive before returning
|
||||||
}
|
}
|
||||||
|
|
||||||
type auditCall struct {
|
type auditCall struct {
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ func TestNewCORS_NilOriginsDeniesAll(t *testing.T) {
|
|||||||
// TestNewCORS_M013_ContractDocumentedInOrder pins the documented dispatch
|
// TestNewCORS_M013_ContractDocumentedInOrder pins the documented dispatch
|
||||||
// order so a refactor cannot silently invert the cases:
|
// order so a refactor cannot silently invert the cases:
|
||||||
//
|
//
|
||||||
// 1. len(AllowedOrigins) == 0 → deny (no CORS headers)
|
// 1. len(AllowedOrigins) == 0 → deny (no CORS headers)
|
||||||
// 2. AllowedOrigins == ["*"] → allow all (Access-Control-Allow-Origin: *)
|
// 2. AllowedOrigins == ["*"] → allow all (Access-Control-Allow-Origin: *)
|
||||||
// 3. else → exact-match allowlist with Vary: Origin
|
// 3. else → exact-match allowlist with Vary: Origin
|
||||||
//
|
//
|
||||||
// If a refactor accidentally falls through to the allow-all branch when
|
// If a refactor accidentally falls through to the allow-all branch when
|
||||||
// AllowedOrigins is empty, this test fails on case 1.
|
// AllowedOrigins is empty, this test fails on case 1.
|
||||||
@@ -293,9 +293,9 @@ func TestNewCORS_MultipleOrigins(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
origin string
|
origin string
|
||||||
shouldAllow bool
|
shouldAllow bool
|
||||||
description string
|
description string
|
||||||
}{
|
}{
|
||||||
{"https://app.example.com", true, "first origin in list"},
|
{"https://app.example.com", true, "first origin in list"},
|
||||||
{"https://admin.example.com", true, "second origin in list"},
|
{"https://admin.example.com", true, "second origin in list"},
|
||||||
|
|||||||
@@ -290,11 +290,11 @@ func NewRateLimiter(cfg RateLimitConfig) func(http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
limiter := &keyedRateLimiter{
|
limiter := &keyedRateLimiter{
|
||||||
ipRate: cfg.RPS,
|
ipRate: cfg.RPS,
|
||||||
ipBurst: float64(cfg.BurstSize),
|
ipBurst: float64(cfg.BurstSize),
|
||||||
userRate: perUserRPS,
|
userRate: perUserRPS,
|
||||||
userBurst: perUserBurst,
|
userBurst: perUserBurst,
|
||||||
buckets: make(map[string]*tokenBucket),
|
buckets: make(map[string]*tokenBucket),
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
|
|||||||
@@ -990,9 +990,9 @@ func TestGetLogLevel_AllLevels(t *testing.T) {
|
|||||||
{"info", slog.LevelInfo},
|
{"info", slog.LevelInfo},
|
||||||
{"warn", slog.LevelWarn},
|
{"warn", slog.LevelWarn},
|
||||||
{"error", slog.LevelError},
|
{"error", slog.LevelError},
|
||||||
{"unknown", slog.LevelInfo}, // default fallback
|
{"unknown", slog.LevelInfo}, // default fallback
|
||||||
{"", slog.LevelInfo}, // empty string
|
{"", slog.LevelInfo}, // empty string
|
||||||
{"DEBUG", slog.LevelInfo}, // case-sensitive, no match → default
|
{"DEBUG", slog.LevelInfo}, // case-sensitive, no match → default
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.level, func(t *testing.T) {
|
t.Run(tt.level, func(t *testing.T) {
|
||||||
@@ -1091,6 +1091,7 @@ func TestGetEnvBool(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// I-003: Job timeout reaper configuration tests
|
// I-003: Job timeout reaper configuration tests
|
||||||
func TestConfig_Scheduler_JobTimeoutDefaults(t *testing.T) {
|
func TestConfig_Scheduler_JobTimeoutDefaults(t *testing.T) {
|
||||||
clearCertctlEnv(t)
|
clearCertctlEnv(t)
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import (
|
|||||||
|
|
||||||
// mockSMClient is a mock implementation of SMClient for testing.
|
// mockSMClient is a mock implementation of SMClient for testing.
|
||||||
type mockSMClient struct {
|
type mockSMClient struct {
|
||||||
secrets map[string]string // secret name -> secret value
|
secrets map[string]string // secret name -> secret value
|
||||||
secretMetadata map[string]SecretMetadata // secret name -> metadata
|
secretMetadata map[string]SecretMetadata // secret name -> metadata
|
||||||
listError error
|
listError error
|
||||||
getErrors map[string]error // secret name -> error
|
getErrors map[string]error // secret name -> error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockSMClient() *mockSMClient {
|
func newMockSMClient() *mockSMClient {
|
||||||
@@ -369,4 +369,3 @@ func TestSource_Discover_AgentIDAndSourcePath(t *testing.T) {
|
|||||||
t.Errorf("expected source path 'aws-sm://eu-west-1/my-secret', got %s", report.Certificates[0].SourcePath)
|
t.Errorf("expected source path 'aws-sm://eu-west-1/my-secret', got %s", report.Certificates[0].SourcePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ type certificateListResponse struct {
|
|||||||
Value []struct {
|
Value []struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Attributes struct {
|
Attributes struct {
|
||||||
Enabled int64 `json:"enabled"`
|
Enabled int64 `json:"enabled"`
|
||||||
Created int64 `json:"created"`
|
Created int64 `json:"created"`
|
||||||
Updated int64 `json:"updated"`
|
Updated int64 `json:"updated"`
|
||||||
Exp int64 `json:"exp"`
|
Exp int64 `json:"exp"`
|
||||||
} `json:"attributes,omitempty"`
|
} `json:"attributes,omitempty"`
|
||||||
Tags map[string]string `json:"tags,omitempty"`
|
Tags map[string]string `json:"tags,omitempty"`
|
||||||
} `json:"value"`
|
} `json:"value"`
|
||||||
@@ -84,10 +84,10 @@ type certificateBundle struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
CER string `json:"cer"`
|
CER string `json:"cer"`
|
||||||
Attributes struct {
|
Attributes struct {
|
||||||
Enabled int64 `json:"enabled"`
|
Enabled int64 `json:"enabled"`
|
||||||
Created int64 `json:"created"`
|
Created int64 `json:"created"`
|
||||||
Updated int64 `json:"updated"`
|
Updated int64 `json:"updated"`
|
||||||
Exp int64 `json:"exp"`
|
Exp int64 `json:"exp"`
|
||||||
} `json:"attributes,omitempty"`
|
} `json:"attributes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,10 +170,10 @@ func (s *Source) Discover(ctx context.Context) (*domain.DiscoveryReport, error)
|
|||||||
s.logger.Info("starting Azure Key Vault discovery", "vault_url", s.config.VaultURL)
|
s.logger.Info("starting Azure Key Vault discovery", "vault_url", s.config.VaultURL)
|
||||||
|
|
||||||
report := &domain.DiscoveryReport{
|
report := &domain.DiscoveryReport{
|
||||||
AgentID: "cloud-azure-kv",
|
AgentID: "cloud-azure-kv",
|
||||||
Directories: []string{fmt.Sprintf("azure-kv://%s/", s.config.VaultURL)},
|
Directories: []string{fmt.Sprintf("azure-kv://%s/", s.config.VaultURL)},
|
||||||
Certificates: []domain.DiscoveredCertEntry{},
|
Certificates: []domain.DiscoveredCertEntry{},
|
||||||
Errors: []string{},
|
Errors: []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func generateTestCertificate(cn string, expire time.Duration) (*x509.Certificate
|
|||||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||||
x509.ExtKeyUsageServerAuth,
|
x509.ExtKeyUsageServerAuth,
|
||||||
},
|
},
|
||||||
DNSNames: []string{"example.com", "*.example.com"},
|
DNSNames: []string{"example.com", "*.example.com"},
|
||||||
EmailAddresses: []string{"test@example.com"},
|
EmailAddresses: []string{"test@example.com"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -798,9 +798,9 @@ func TestValidateConfig_DNSPersistIssuerDomainRequired(t *testing.T) {
|
|||||||
|
|
||||||
c := New(nil, testLogger())
|
c := New(nil, testLogger())
|
||||||
cfg, _ := json.Marshal(map[string]string{
|
cfg, _ := json.Marshal(map[string]string{
|
||||||
"directory_url": srv.URL,
|
"directory_url": srv.URL,
|
||||||
"email": "test@example.com",
|
"email": "test@example.com",
|
||||||
"challenge_type": "dns-persist-01",
|
"challenge_type": "dns-persist-01",
|
||||||
"dns_present_script": "/tmp/script.sh",
|
"dns_present_script": "/tmp/script.sh",
|
||||||
// Missing dns_persist_issuer_domain
|
// Missing dns_persist_issuer_domain
|
||||||
})
|
})
|
||||||
@@ -870,9 +870,9 @@ func TestValidateConfig_DNS01WithPresentScript(t *testing.T) {
|
|||||||
|
|
||||||
c := New(nil, testLogger())
|
c := New(nil, testLogger())
|
||||||
cfg, _ := json.Marshal(map[string]string{
|
cfg, _ := json.Marshal(map[string]string{
|
||||||
"directory_url": srv.URL,
|
"directory_url": srv.URL,
|
||||||
"email": "test@example.com",
|
"email": "test@example.com",
|
||||||
"challenge_type": "dns-01",
|
"challenge_type": "dns-01",
|
||||||
"dns_present_script": "/bin/sh",
|
"dns_present_script": "/bin/sh",
|
||||||
"dns_cleanup_script": "/bin/sh",
|
"dns_cleanup_script": "/bin/sh",
|
||||||
})
|
})
|
||||||
@@ -897,10 +897,10 @@ func TestValidateConfig_DNSPersist01WithAllFields(t *testing.T) {
|
|||||||
|
|
||||||
c := New(nil, testLogger())
|
c := New(nil, testLogger())
|
||||||
cfg, _ := json.Marshal(map[string]string{
|
cfg, _ := json.Marshal(map[string]string{
|
||||||
"directory_url": srv.URL,
|
"directory_url": srv.URL,
|
||||||
"email": "test@example.com",
|
"email": "test@example.com",
|
||||||
"challenge_type": "dns-persist-01",
|
"challenge_type": "dns-persist-01",
|
||||||
"dns_present_script": "/bin/sh",
|
"dns_present_script": "/bin/sh",
|
||||||
"dns_persist_issuer_domain": "letsencrypt.org",
|
"dns_persist_issuer_domain": "letsencrypt.org",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ func TestGetRenewalInfo_NotFound(t *testing.T) {
|
|||||||
if r.URL.Path == "/directory" && r.Method == http.MethodGet {
|
if r.URL.Path == "/directory" && r.Method == http.MethodGet {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
"newOrder": "/acme/new-order",
|
"newOrder": "/acme/new-order",
|
||||||
"newAccount": "/acme/new-account",
|
"newAccount": "/acme/new-account",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -115,8 +115,8 @@ func TestGetRenewalInfo_ServerError(t *testing.T) {
|
|||||||
if r.URL.Path == "/directory" && r.Method == http.MethodGet {
|
if r.URL.Path == "/directory" && r.Method == http.MethodGet {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
"newOrder": "/acme/new-order",
|
"newOrder": "/acme/new-order",
|
||||||
"newAccount": "/acme/new-account",
|
"newAccount": "/acme/new-account",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ type pebbleMockServer struct {
|
|||||||
idSeq int64
|
idSeq int64
|
||||||
// Behavior toggles for failure-mode tests.
|
// Behavior toggles for failure-mode tests.
|
||||||
failNewAccount bool
|
failNewAccount bool
|
||||||
rateLimitedOrder int32 // atomic counter; non-zero ⇒ first N orders return 429
|
rateLimitedOrder int32 // atomic counter; non-zero ⇒ first N orders return 429
|
||||||
finalizeReturns string // "" (default), "processing-stuck", "invalid"
|
finalizeReturns string // "" (default), "processing-stuck", "invalid"
|
||||||
authzPending bool // when true, new authzs start as "pending" and only flip to "valid" after the challenge endpoint is POSTed
|
authzPending bool // when true, new authzs start as "pending" and only flip to "valid" after the challenge endpoint is POSTed
|
||||||
challengeType string // when set, the per-authz challenge type emitted (default "http-01")
|
challengeType string // when set, the per-authz challenge type emitted (default "http-01")
|
||||||
@@ -990,12 +990,12 @@ func TestPebbleMock_ContextCancel_DuringIssuance(t *testing.T) {
|
|||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
type mockDNSSolver struct {
|
type mockDNSSolver struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
presented map[string]string // domain → keyAuth (or recordValue)
|
presented map[string]string // domain → keyAuth (or recordValue)
|
||||||
cleanedUp map[string]bool
|
cleanedUp map[string]bool
|
||||||
presentErr error
|
presentErr error
|
||||||
cleanErr error
|
cleanErr error
|
||||||
presentDelay time.Duration
|
presentDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockDNSSolver() *mockDNSSolver {
|
func newMockDNSSolver() *mockDNSSolver {
|
||||||
|
|||||||
@@ -249,4 +249,3 @@ func signJWS(key *ecdsa.PrivateKey, kid, nonce, targetURL string, payload []byte
|
|||||||
|
|
||||||
return json.Marshal(jws)
|
return json.Marshal(jws)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ type GetCertificateOutput struct {
|
|||||||
|
|
||||||
// RevokeCertificateInput represents the request to revoke a certificate.
|
// RevokeCertificateInput represents the request to revoke a certificate.
|
||||||
type RevokeCertificateInput struct {
|
type RevokeCertificateInput struct {
|
||||||
CAArn string
|
CAArn string
|
||||||
CertificateSerial string
|
CertificateSerial string
|
||||||
RevocationReason string
|
RevocationReason string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCACertificateInput represents the request to retrieve the CA certificate.
|
// GetCACertificateInput represents the request to retrieve the CA certificate.
|
||||||
@@ -395,14 +395,14 @@ func mapRevocationReason(reason *string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reasonMap := map[string]string{
|
reasonMap := map[string]string{
|
||||||
"unspecified": "UNSPECIFIED",
|
"unspecified": "UNSPECIFIED",
|
||||||
"keyCompromise": "KEY_COMPROMISE",
|
"keyCompromise": "KEY_COMPROMISE",
|
||||||
"caCompromise": "CERTIFICATE_AUTHORITY_COMPROMISE",
|
"caCompromise": "CERTIFICATE_AUTHORITY_COMPROMISE",
|
||||||
"affiliationChanged": "AFFILIATION_CHANGED",
|
"affiliationChanged": "AFFILIATION_CHANGED",
|
||||||
"superseded": "SUPERSEDED",
|
"superseded": "SUPERSEDED",
|
||||||
"cessationOfOperation": "CESSATION_OF_OPERATION",
|
"cessationOfOperation": "CESSATION_OF_OPERATION",
|
||||||
"certificateHold": "CERTIFICATE_HOLD",
|
"certificateHold": "CERTIFICATE_HOLD",
|
||||||
"privilegeWithdrawn": "PRIVILEGE_WITHDRAWN",
|
"privilegeWithdrawn": "PRIVILEGE_WITHDRAWN",
|
||||||
}
|
}
|
||||||
|
|
||||||
if mapped, ok := reasonMap[*reason]; ok {
|
if mapped, ok := reasonMap[*reason]; ok {
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ import (
|
|||||||
|
|
||||||
// mockACMPCAClient implements the ACMPCAClient interface for testing.
|
// mockACMPCAClient implements the ACMPCAClient interface for testing.
|
||||||
type mockACMPCAClient struct {
|
type mockACMPCAClient struct {
|
||||||
issueCertificateErr error
|
issueCertificateErr error
|
||||||
getCertificateErr error
|
getCertificateErr error
|
||||||
revokeCertificateErr error
|
revokeCertificateErr error
|
||||||
getCACertificateErr error
|
getCACertificateErr error
|
||||||
issuedCertPEM string
|
issuedCertPEM string
|
||||||
issuedChainPEM string
|
issuedChainPEM string
|
||||||
caCertPEM string
|
caCertPEM string
|
||||||
caCertChainPEM string
|
caCertChainPEM string
|
||||||
lastIssueCertificateInput *awsacmpca.IssueCertificateInput
|
lastIssueCertificateInput *awsacmpca.IssueCertificateInput
|
||||||
lastRevokeCertificateInput *awsacmpca.RevokeCertificateInput
|
lastRevokeCertificateInput *awsacmpca.RevokeCertificateInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,9 +90,9 @@ func New(config *Config, logger *slog.Logger) *Connector {
|
|||||||
|
|
||||||
// orderRequest is the JSON body for DigiCert certificate order submission.
|
// orderRequest is the JSON body for DigiCert certificate order submission.
|
||||||
type orderRequest struct {
|
type orderRequest struct {
|
||||||
Certificate orderCert `json:"certificate"`
|
Certificate orderCert `json:"certificate"`
|
||||||
Organization orderOrg `json:"organization"`
|
Organization orderOrg `json:"organization"`
|
||||||
ValidityYears int `json:"validity_years"`
|
ValidityYears int `json:"validity_years"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type orderCert struct {
|
type orderCert struct {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func (c *Connector) IssueCertificate(ctx context.Context, request issuer.Issuanc
|
|||||||
csrBase64 := base64.StdEncoding.EncodeToString(csrBlock.Bytes)
|
csrBase64 := base64.StdEncoding.EncodeToString(csrBlock.Bytes)
|
||||||
|
|
||||||
enrollReq := map[string]interface{}{
|
enrollReq := map[string]interface{}{
|
||||||
"certificate_request": csrBase64,
|
"certificate_request": csrBase64,
|
||||||
"certificate_authority_name": c.config.CAName,
|
"certificate_authority_name": c.config.CAName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ func TestEJBCAConnector(t *testing.T) {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
respData := map[string]interface{}{
|
respData := map[string]interface{}{
|
||||||
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
|
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
|
||||||
"certificate_chain": []string{base64.StdEncoding.EncodeToString(chainBlock.Bytes)},
|
"certificate_chain": []string{base64.StdEncoding.EncodeToString(chainBlock.Bytes)},
|
||||||
"serial_number": "123456",
|
"serial_number": "123456",
|
||||||
}
|
}
|
||||||
@@ -242,7 +242,7 @@ func TestEJBCAConnector(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
respData := map[string]interface{}{
|
respData := map[string]interface{}{
|
||||||
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
|
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
|
||||||
"certificate_chain": []string{},
|
"certificate_chain": []string{},
|
||||||
"serial_number": "789012",
|
"serial_number": "789012",
|
||||||
}
|
}
|
||||||
@@ -314,7 +314,7 @@ func TestEJBCAConnector(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
respData := map[string]interface{}{
|
respData := map[string]interface{}{
|
||||||
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
|
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
|
||||||
"certificate_chain": []string{},
|
"certificate_chain": []string{},
|
||||||
"serial_number": "123456",
|
"serial_number": "123456",
|
||||||
}
|
}
|
||||||
@@ -356,7 +356,7 @@ func TestEJBCAConnector(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
respData := map[string]interface{}{
|
respData := map[string]interface{}{
|
||||||
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
|
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
|
||||||
"certificate_chain": []string{},
|
"certificate_chain": []string{},
|
||||||
"serial_number": "654321",
|
"serial_number": "654321",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ func NewWithHTTPClient(config *Config, logger *slog.Logger, client *http.Client)
|
|||||||
|
|
||||||
// enrollmentRequest is the JSON body for Entrust enrollment submission.
|
// enrollmentRequest is the JSON body for Entrust enrollment submission.
|
||||||
type enrollmentRequest struct {
|
type enrollmentRequest struct {
|
||||||
CSR string `json:"csr"`
|
CSR string `json:"csr"`
|
||||||
ProfileId string `json:"profileId,omitempty"`
|
ProfileId string `json:"profileId,omitempty"`
|
||||||
SubjectAltNames []san `json:"subjectAltNames,omitempty"`
|
SubjectAltNames []san `json:"subjectAltNames,omitempty"`
|
||||||
CertificateAuthority string `json:"certificateAuthority,omitempty"`
|
CertificateAuthority string `json:"certificateAuthority,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,9 +107,9 @@ func NewWithHTTPClient(config *Config, logger *slog.Logger, client *http.Client)
|
|||||||
|
|
||||||
// certificateRequest is the JSON body for GlobalSign certificate order submission.
|
// certificateRequest is the JSON body for GlobalSign certificate order submission.
|
||||||
type certificateRequest struct {
|
type certificateRequest struct {
|
||||||
CSR string `json:"csr"`
|
CSR string `json:"csr"`
|
||||||
SubjectDN subjectDNRequest `json:"subject_dn"`
|
SubjectDN subjectDNRequest `json:"subject_dn"`
|
||||||
SAN sanRequest `json:"san,omitempty"`
|
SAN sanRequest `json:"san,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type subjectDNRequest struct {
|
type subjectDNRequest struct {
|
||||||
|
|||||||
@@ -93,10 +93,10 @@ type Connector struct {
|
|||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
|
|
||||||
// OAuth2 token caching
|
// OAuth2 token caching
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
tokenCache *cachedToken
|
tokenCache *cachedToken
|
||||||
saKey *serviceAccountKey
|
saKey *serviceAccountKey
|
||||||
rsaKey *rsa.PrivateKey
|
rsaKey *rsa.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Google CAS connector with the given configuration and logger.
|
// New creates a new Google CAS connector with the given configuration and logger.
|
||||||
|
|||||||
@@ -479,9 +479,9 @@ func (c *Connector) parseCertificate(certPEM []byte) (*x509.Certificate, string,
|
|||||||
// Format: [{"serial": "...", "revoked_at": "...", "reason_code": ...}, ...]
|
// Format: [{"serial": "...", "revoked_at": "...", "reason_code": ...}, ...]
|
||||||
func (c *Connector) marshalRevokedSerials(revokedCerts []issuer.RevokedCertEntry) ([]byte, error) {
|
func (c *Connector) marshalRevokedSerials(revokedCerts []issuer.RevokedCertEntry) ([]byte, error) {
|
||||||
type RevokedEntry struct {
|
type RevokedEntry struct {
|
||||||
Serial string `json:"serial"`
|
Serial string `json:"serial"`
|
||||||
RevokedAt string `json:"revoked_at"`
|
RevokedAt string `json:"revoked_at"`
|
||||||
ReasonCode int `json:"reason_code"`
|
ReasonCode int `json:"reason_code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
entries := make([]RevokedEntry, len(revokedCerts))
|
entries := make([]RevokedEntry, len(revokedCerts))
|
||||||
|
|||||||
@@ -643,7 +643,7 @@ func generateTestCSR(cn string) (*x509.CertificateRequest, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
csrTemplate := x509.CertificateRequest{
|
csrTemplate := x509.CertificateRequest{
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
DNSNames: []string{cn, "www." + cn},
|
DNSNames: []string{cn, "www." + cn},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -168,9 +168,9 @@ type signRequest struct {
|
|||||||
|
|
||||||
// signResponse is the JSON response from the step-ca /sign endpoint.
|
// signResponse is the JSON response from the step-ca /sign endpoint.
|
||||||
type signResponse struct {
|
type signResponse struct {
|
||||||
ServerPEM certificateChain `json:"serverPEM,omitempty"`
|
ServerPEM certificateChain `json:"serverPEM,omitempty"`
|
||||||
CaPEM certificateChain `json:"caPEM,omitempty"`
|
CaPEM certificateChain `json:"caPEM,omitempty"`
|
||||||
CertChainPEM []certBlock `json:"certChainPEM,omitempty"`
|
CertChainPEM []certBlock `json:"certChainPEM,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type certificateChain struct {
|
type certificateChain struct {
|
||||||
@@ -380,14 +380,14 @@ func (c *Connector) generateProvisionerToken(subject string, sans []string) (str
|
|||||||
|
|
||||||
// step-ca expects: aud = <ca-url>/1.0/sign (the sign endpoint audience)
|
// step-ca expects: aud = <ca-url>/1.0/sign (the sign endpoint audience)
|
||||||
claims := map[string]interface{}{
|
claims := map[string]interface{}{
|
||||||
"sub": subject,
|
"sub": subject,
|
||||||
"iss": c.config.ProvisionerName,
|
"iss": c.config.ProvisionerName,
|
||||||
"aud": c.config.CAURL + "/1.0/sign",
|
"aud": c.config.CAURL + "/1.0/sign",
|
||||||
"nbf": now.Unix(),
|
"nbf": now.Unix(),
|
||||||
"iat": now.Unix(),
|
"iat": now.Unix(),
|
||||||
"exp": now.Add(5 * time.Minute).Unix(),
|
"exp": now.Add(5 * time.Minute).Unix(),
|
||||||
"jti": generateJTI(),
|
"jti": generateJTI(),
|
||||||
"sha": kid, // step-ca uses this to look up the provisioner by key fingerprint
|
"sha": kid, // step-ca uses this to look up the provisioner by key fingerprint
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sans) > 0 {
|
if len(sans) > 0 {
|
||||||
|
|||||||
@@ -1574,9 +1574,9 @@ func TestLoadProvisionerKey_FileNotReadable(t *testing.T) {
|
|||||||
|
|
||||||
// Test with a provisioner key path that can't be read
|
// Test with a provisioner key path that can't be read
|
||||||
config := stepca.Config{
|
config := stepca.Config{
|
||||||
CAURL: srv.URL,
|
CAURL: srv.URL,
|
||||||
ProvisionerName: "test-provisioner",
|
ProvisionerName: "test-provisioner",
|
||||||
ProvisionerKeyPath: "/root/.ssh/no_such_key", // Permission denied or doesn't exist
|
ProvisionerKeyPath: "/root/.ssh/no_such_key", // Permission denied or doesn't exist
|
||||||
ProvisionerPassword: "password",
|
ProvisionerPassword: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1770,4 +1770,3 @@ func TestIntegration_FullLifecycle(t *testing.T) {
|
|||||||
t.Errorf("Expected status 'completed', got '%s'", status.Status)
|
t.Errorf("Expected status 'completed', got '%s'", status.Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,9 +86,9 @@ func New(config *Config, logger *slog.Logger) *Connector {
|
|||||||
|
|
||||||
// vaultResponse is the standard Vault API response wrapper.
|
// vaultResponse is the standard Vault API response wrapper.
|
||||||
type vaultResponse struct {
|
type vaultResponse struct {
|
||||||
Data json.RawMessage `json:"data"`
|
Data json.RawMessage `json:"data"`
|
||||||
Errors []string `json:"errors,omitempty"`
|
Errors []string `json:"errors,omitempty"`
|
||||||
Warnings []string `json:"warnings,omitempty"`
|
Warnings []string `json:"warnings,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// signData holds the data returned from the /sign endpoint.
|
// signData holds the data returned from the /sign endpoint.
|
||||||
|
|||||||
@@ -88,11 +88,11 @@ func TestEmail_ValidateConfig_MissingPort(t *testing.T) {
|
|||||||
|
|
||||||
func TestEmail_ValidateConfig_MissingFromAddress(t *testing.T) {
|
func TestEmail_ValidateConfig_MissingFromAddress(t *testing.T) {
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
SMTPHost: "smtp.example.com",
|
SMTPHost: "smtp.example.com",
|
||||||
SMTPPort: 587,
|
SMTPPort: 587,
|
||||||
Username: "user",
|
Username: "user",
|
||||||
Password: "pass",
|
Password: "pass",
|
||||||
UseTLS: true,
|
UseTLS: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
rawConfig, _ := json.Marshal(cfg)
|
rawConfig, _ := json.Marshal(cfg)
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ import (
|
|||||||
// to a directory that Envoy watches via its SDS (Secret Discovery Service)
|
// to a directory that Envoy watches via its SDS (Secret Discovery Service)
|
||||||
// file-based configuration or static filename references in the bootstrap config.
|
// file-based configuration or static filename references in the bootstrap config.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
CertDir string `json:"cert_dir"` // Directory where Envoy watches for cert files (required)
|
CertDir string `json:"cert_dir"` // Directory where Envoy watches for cert files (required)
|
||||||
CertFilename string `json:"cert_filename"` // Filename for certificate (default: cert.pem)
|
CertFilename string `json:"cert_filename"` // Filename for certificate (default: cert.pem)
|
||||||
KeyFilename string `json:"key_filename"` // Filename for private key (default: key.pem)
|
KeyFilename string `json:"key_filename"` // Filename for private key (default: key.pem)
|
||||||
ChainFilename string `json:"chain_filename"` // Optional filename for chain (if set, chain written separately)
|
ChainFilename string `json:"chain_filename"` // Optional filename for chain (if set, chain written separately)
|
||||||
SDSConfig bool `json:"sds_config"` // If true, write an SDS discovery JSON file for file-based SDS
|
SDSConfig bool `json:"sds_config"` // If true, write an SDS discovery JSON file for file-based SDS
|
||||||
}
|
}
|
||||||
|
|
||||||
// SDSResource represents an Envoy SDS tls_certificate resource for file-based SDS.
|
// SDSResource represents an Envoy SDS tls_certificate resource for file-based SDS.
|
||||||
@@ -34,9 +34,9 @@ type SDSResource struct {
|
|||||||
|
|
||||||
// SDSTLSCertificate represents a single SDS tls_certificate entry.
|
// SDSTLSCertificate represents a single SDS tls_certificate entry.
|
||||||
type SDSTLSCertificate struct {
|
type SDSTLSCertificate struct {
|
||||||
Type string `json:"@type"`
|
Type string `json:"@type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TLSCertificate TLSCertificate `json:"tls_certificate"`
|
TLSCertificate TLSCertificate `json:"tls_certificate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLSCertificate contains the file paths for cert and key in Envoy's SDS format.
|
// TLSCertificate contains the file paths for cert and key in Envoy's SDS format.
|
||||||
|
|||||||
@@ -457,13 +457,13 @@ func (c *Connector) DeployCertificate(ctx context.Context, request target.Deploy
|
|||||||
Message: "Certificate uploaded and SSL profile updated via iControl REST",
|
Message: "Certificate uploaded and SSL profile updated via iControl REST",
|
||||||
DeployedAt: time.Now(),
|
DeployedAt: time.Now(),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
"host": c.config.Host,
|
"host": c.config.Host,
|
||||||
"partition": c.config.Partition,
|
"partition": c.config.Partition,
|
||||||
"ssl_profile": c.config.SSLProfile,
|
"ssl_profile": c.config.SSLProfile,
|
||||||
"cert_object_name": certName,
|
"cert_object_name": certName,
|
||||||
"key_object_name": keyName,
|
"key_object_name": keyName,
|
||||||
"chain_object_name": chainName,
|
"chain_object_name": chainName,
|
||||||
"duration_ms": fmt.Sprintf("%d", deploymentDuration.Milliseconds()),
|
"duration_ms": fmt.Sprintf("%d", deploymentDuration.Milliseconds()),
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -561,12 +561,12 @@ func (c *Connector) ValidateDeployment(ctx context.Context, request target.Valid
|
|||||||
Message: fmt.Sprintf("SSL profile %q has cert %q configured", c.config.SSLProfile, profile.Cert),
|
Message: fmt.Sprintf("SSL profile %q has cert %q configured", c.config.SSLProfile, profile.Cert),
|
||||||
ValidatedAt: time.Now(),
|
ValidatedAt: time.Now(),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
"host": c.config.Host,
|
"host": c.config.Host,
|
||||||
"ssl_profile": c.config.SSLProfile,
|
"ssl_profile": c.config.SSLProfile,
|
||||||
"current_cert": profile.Cert,
|
"current_cert": profile.Cert,
|
||||||
"current_key": profile.Key,
|
"current_key": profile.Key,
|
||||||
"current_chain": profile.Chain,
|
"current_chain": profile.Chain,
|
||||||
"duration_ms": fmt.Sprintf("%d", validationDuration.Milliseconds()),
|
"duration_ms": fmt.Sprintf("%d", validationDuration.Milliseconds()),
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ type mockF5Client struct {
|
|||||||
calls []mockCall
|
calls []mockCall
|
||||||
|
|
||||||
// Configurable responses per method
|
// Configurable responses per method
|
||||||
authenticateErr error
|
authenticateErr error
|
||||||
authenticateCount int // tracks number of Authenticate calls
|
authenticateCount int // tracks number of Authenticate calls
|
||||||
uploadFileErr error
|
uploadFileErr error
|
||||||
uploadFileErrOn string // only error when filename contains this substring
|
uploadFileErrOn string // only error when filename contains this substring
|
||||||
installCertErr error
|
installCertErr error
|
||||||
installCertErrOn string
|
installCertErrOn string
|
||||||
installKeyErr error
|
installKeyErr error
|
||||||
createTransactionID string
|
createTransactionID string
|
||||||
createTransactionErr error
|
createTransactionErr error
|
||||||
commitTransactionErr error
|
commitTransactionErr error
|
||||||
updateSSLProfileErr error
|
updateSSLProfileErr error
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ func newWinRMExecutor(cfg *WinRMConfig) (*winrmExecutor, error) {
|
|||||||
port,
|
port,
|
||||||
cfg.UseHTTPS,
|
cfg.UseHTTPS,
|
||||||
cfg.Insecure,
|
cfg.Insecure,
|
||||||
nil, // CA cert
|
nil, // CA cert
|
||||||
nil, // Client cert
|
nil, // Client cert
|
||||||
nil, // Client key
|
nil, // Client key
|
||||||
timeout,
|
timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -263,10 +263,10 @@ func (c *Connector) DeployCertificate(ctx context.Context, request target.Deploy
|
|||||||
Message: fmt.Sprintf("Certificate imported to %s (alias: %s, thumbprint: %s)", c.config.KeystorePath, c.config.Alias, thumbprint),
|
Message: fmt.Sprintf("Certificate imported to %s (alias: %s, thumbprint: %s)", c.config.KeystorePath, c.config.Alias, thumbprint),
|
||||||
DeployedAt: time.Now(),
|
DeployedAt: time.Now(),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
"thumbprint": thumbprint,
|
"thumbprint": thumbprint,
|
||||||
"alias": c.config.Alias,
|
"alias": c.config.Alias,
|
||||||
"keystore_type": c.config.KeystoreType,
|
"keystore_type": c.config.KeystoreType,
|
||||||
"keystore_path": c.config.KeystorePath,
|
"keystore_path": c.config.KeystorePath,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ func TestDeployCertificate_Success(t *testing.T) {
|
|||||||
|
|
||||||
mock := &mockExecutor{
|
mock := &mockExecutor{
|
||||||
responses: []mockResponse{
|
responses: []mockResponse{
|
||||||
{Output: "", Err: nil}, // keytool -delete (alias may not exist)
|
{Output: "", Err: nil}, // keytool -delete (alias may not exist)
|
||||||
{Output: "Import command completed", Err: nil}, // keytool -importkeystore
|
{Output: "Import command completed", Err: nil}, // keytool -importkeystore
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -355,8 +355,8 @@ func TestDeployCertificate_WithReload(t *testing.T) {
|
|||||||
mock := &mockExecutor{
|
mock := &mockExecutor{
|
||||||
responses: []mockResponse{
|
responses: []mockResponse{
|
||||||
// No existing keystore → delete skipped → import is call 0, reload is call 1
|
// No existing keystore → delete skipped → import is call 0, reload is call 1
|
||||||
{Output: "Imported", Err: nil}, // import
|
{Output: "Imported", Err: nil}, // import
|
||||||
{Output: "restarted", Err: nil}, // reload
|
{Output: "restarted", Err: nil}, // reload
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c := NewWithExecutor(&Config{
|
c := NewWithExecutor(&Config{
|
||||||
@@ -391,8 +391,8 @@ func TestDeployCertificate_ReloadFailed_NonFatal(t *testing.T) {
|
|||||||
|
|
||||||
mock := &mockExecutor{
|
mock := &mockExecutor{
|
||||||
responses: []mockResponse{
|
responses: []mockResponse{
|
||||||
{Output: "", Err: nil}, // delete
|
{Output: "", Err: nil}, // delete
|
||||||
{Output: "Imported", Err: nil}, // import
|
{Output: "Imported", Err: nil}, // import
|
||||||
{Output: "Failed to restart", Err: fmt.Errorf("exit 1")}, // reload fails
|
{Output: "Failed to restart", Err: fmt.Errorf("exit 1")}, // reload fails
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ import (
|
|||||||
// Supports in-cluster auth by default (ServiceAccount token auto-mounted) or
|
// Supports in-cluster auth by default (ServiceAccount token auto-mounted) or
|
||||||
// out-of-cluster auth via kubeconfig file.
|
// out-of-cluster auth via kubeconfig file.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Namespace string `json:"namespace"` // Required. Kubernetes namespace.
|
Namespace string `json:"namespace"` // Required. Kubernetes namespace.
|
||||||
SecretName string `json:"secret_name"` // Required. Name of the kubernetes.io/tls Secret.
|
SecretName string `json:"secret_name"` // Required. Name of the kubernetes.io/tls Secret.
|
||||||
Labels map[string]string `json:"labels,omitempty"` // Optional. Additional labels to add to the Secret.
|
Labels map[string]string `json:"labels,omitempty"` // Optional. Additional labels to add to the Secret.
|
||||||
KubeconfigPath string `json:"kubeconfig_path,omitempty"` // Optional. Path to kubeconfig for out-of-cluster auth.
|
KubeconfigPath string `json:"kubeconfig_path,omitempty"` // Optional. Path to kubeconfig for out-of-cluster auth.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func (m *mockK8sClient) DeleteSecret(ctx context.Context, namespace, name string
|
|||||||
|
|
||||||
func TestValidateConfig_Success_MinimalConfig(t *testing.T) {
|
func TestValidateConfig_Success_MinimalConfig(t *testing.T) {
|
||||||
cfg := map[string]interface{}{
|
cfg := map[string]interface{}{
|
||||||
"namespace": "default",
|
"namespace": "default",
|
||||||
"secret_name": "my-cert",
|
"secret_name": "my-cert",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,4 +644,3 @@ func contains(s, substr string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -411,9 +411,9 @@ func (c *realSSHClient) Connect(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sshConfig := &ssh.ClientConfig{
|
sshConfig := &ssh.ClientConfig{
|
||||||
User: c.config.User,
|
User: c.config.User,
|
||||||
Auth: authMethods,
|
Auth: authMethods,
|
||||||
Timeout: time.Duration(c.config.Timeout) * time.Second,
|
Timeout: time.Duration(c.config.Timeout) * time.Second,
|
||||||
// InsecureIgnoreHostKey is used intentionally: certctl deploys to known
|
// InsecureIgnoreHostKey is used intentionally: certctl deploys to known
|
||||||
// infrastructure (the operator explicitly configures each target host).
|
// infrastructure (the operator explicitly configures each target host).
|
||||||
// This is the same security rationale as network scanner's InsecureSkipVerify
|
// This is the same security rationale as network scanner's InsecureSkipVerify
|
||||||
|
|||||||
@@ -42,15 +42,15 @@ type fakeSSHServer struct {
|
|||||||
user string
|
user string
|
||||||
password string
|
password string
|
||||||
|
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
closed bool
|
closed bool
|
||||||
|
|
||||||
// Optional behaviour toggles for failure-mode tests.
|
// Optional behaviour toggles for failure-mode tests.
|
||||||
rejectAuth bool // reject all auth attempts (auth failure path)
|
rejectAuth bool // reject all auth attempts (auth failure path)
|
||||||
dropOnHandshake bool // close conn before SSH NewServerConn returns (handshake failure)
|
dropOnHandshake bool // close conn before SSH NewServerConn returns (handshake failure)
|
||||||
failExec bool // exec sessions return non-zero exit (Execute error path)
|
failExec bool // exec sessions return non-zero exit (Execute error path)
|
||||||
failSFTP bool // refuse sftp subsystem (SFTP failure path)
|
failSFTP bool // refuse sftp subsystem (SFTP failure path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startFakeSSHServer binds a fresh server on a random local port and returns
|
// startFakeSSHServer binds a fresh server on a random local port and returns
|
||||||
|
|||||||
@@ -310,4 +310,3 @@ func (c *Connector) ValidateDeployment(ctx context.Context, request target.Valid
|
|||||||
|
|
||||||
// Ensure Connector implements target.Connector.
|
// Ensure Connector implements target.Connector.
|
||||||
var _ target.Connector = (*Connector)(nil)
|
var _ target.Connector = (*Connector)(nil)
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ func testLogger() *slog.Logger {
|
|||||||
|
|
||||||
// mockExecutor records PowerShell scripts and returns configurable responses.
|
// mockExecutor records PowerShell scripts and returns configurable responses.
|
||||||
type mockExecutor struct {
|
type mockExecutor struct {
|
||||||
scripts []string
|
scripts []string
|
||||||
responses []string
|
responses []string
|
||||||
errors []error
|
errors []error
|
||||||
callIndex int
|
callIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockExecutor) Execute(ctx context.Context, script string) (string, error) {
|
func (m *mockExecutor) Execute(ctx context.Context, script string) (string, error) {
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import "testing"
|
|||||||
|
|
||||||
func TestCertificateStatus_Constants(t *testing.T) {
|
func TestCertificateStatus_Constants(t *testing.T) {
|
||||||
tests := map[string]CertificateStatus{
|
tests := map[string]CertificateStatus{
|
||||||
"Pending": CertificateStatusPending,
|
"Pending": CertificateStatusPending,
|
||||||
"Active": CertificateStatusActive,
|
"Active": CertificateStatusActive,
|
||||||
"Expiring": CertificateStatusExpiring,
|
"Expiring": CertificateStatusExpiring,
|
||||||
"Expired": CertificateStatusExpired,
|
"Expired": CertificateStatusExpired,
|
||||||
"RenewalInProgress": CertificateStatusRenewalInProgress,
|
"RenewalInProgress": CertificateStatusRenewalInProgress,
|
||||||
"Failed": CertificateStatusFailed,
|
"Failed": CertificateStatusFailed,
|
||||||
"Revoked": CertificateStatusRevoked,
|
"Revoked": CertificateStatusRevoked,
|
||||||
"Archived": CertificateStatusArchived,
|
"Archived": CertificateStatusArchived,
|
||||||
}
|
}
|
||||||
for expected, got := range tests {
|
for expected, got := range tests {
|
||||||
if string(got) != expected {
|
if string(got) != expected {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type Issuer struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type IssuerType `json:"type"`
|
Type IssuerType `json:"type"`
|
||||||
Config json.RawMessage `json:"config"`
|
Config json.RawMessage `json:"config"`
|
||||||
EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API)
|
EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API)
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
|
LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
|
||||||
TestStatus string `json:"test_status,omitempty"`
|
TestStatus string `json:"test_status,omitempty"`
|
||||||
@@ -27,13 +27,13 @@ type DeploymentTarget struct {
|
|||||||
Type TargetType `json:"type"`
|
Type TargetType `json:"type"`
|
||||||
AgentID string `json:"agent_id"`
|
AgentID string `json:"agent_id"`
|
||||||
Config json.RawMessage `json:"config"`
|
Config json.RawMessage `json:"config"`
|
||||||
EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API)
|
EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API)
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
|
LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
|
||||||
TestStatus string `json:"test_status,omitempty"`
|
TestStatus string `json:"test_status,omitempty"`
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitempty"`
|
||||||
RetiredAt *time.Time `json:"retired_at,omitempty"` // I-004: soft-retirement timestamp (nil = active)
|
RetiredAt *time.Time `json:"retired_at,omitempty"` // I-004: soft-retirement timestamp (nil = active)
|
||||||
RetiredReason *string `json:"retired_reason,omitempty"` // I-004: reason captured at cascade retirement
|
RetiredReason *string `json:"retired_reason,omitempty"` // I-004: reason captured at cascade retirement
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
@@ -65,11 +65,11 @@ type Agent struct {
|
|||||||
// docs/architecture.md ER diagram (which documents DB shape, not API
|
// docs/architecture.md ER diagram (which documents DB shape, not API
|
||||||
// shape) and coverage-gap-audit-2026-04-24-v5/unified-audit.md
|
// shape) and coverage-gap-audit-2026-04-24-v5/unified-audit.md
|
||||||
// cat-s5-apikey_leak for the full closure rationale.
|
// cat-s5-apikey_leak for the full closure rationale.
|
||||||
APIKeyHash string `json:"-"`
|
APIKeyHash string `json:"-"`
|
||||||
OS string `json:"os"`
|
OS string `json:"os"`
|
||||||
Architecture string `json:"architecture"`
|
Architecture string `json:"architecture"`
|
||||||
IPAddress string `json:"ip_address"`
|
IPAddress string `json:"ip_address"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
// I-004: soft-retirement fields. An agent with RetiredAt != nil is the
|
// I-004: soft-retirement fields. An agent with RetiredAt != nil is the
|
||||||
// canonical "retired" state. The Status column remains as before (Online
|
// canonical "retired" state. The Status column remains as before (Online
|
||||||
// / Offline / Degraded) and is preserved at retirement time as the
|
// / Offline / Degraded) and is preserved at retirement time as the
|
||||||
@@ -115,9 +115,9 @@ func (a *Agent) IsRetired() bool { return a != nil && a.RetiredAt != nil }
|
|||||||
// any non-zero count blocks a default retire with HTTP 409 and requires an
|
// any non-zero count blocks a default retire with HTTP 409 and requires an
|
||||||
// explicit ?force=true&reason=... escape hatch from the operator.
|
// explicit ?force=true&reason=... escape hatch from the operator.
|
||||||
type AgentDependencyCounts struct {
|
type AgentDependencyCounts struct {
|
||||||
ActiveTargets int `json:"active_targets"` // deployment_targets.agent_id=id AND retired_at IS NULL
|
ActiveTargets int `json:"active_targets"` // deployment_targets.agent_id=id AND retired_at IS NULL
|
||||||
ActiveCertificates int `json:"active_certificates"` // certificates currently deployed via one of this agent's active targets
|
ActiveCertificates int `json:"active_certificates"` // certificates currently deployed via one of this agent's active targets
|
||||||
PendingJobs int `json:"pending_jobs"` // jobs.agent_id=id AND status IN (Pending, AwaitingCSR, AwaitingApproval, Running)
|
PendingJobs int `json:"pending_jobs"` // jobs.agent_id=id AND status IN (Pending, AwaitingCSR, AwaitingApproval, Running)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasDependencies reports whether any preflight counter is non-zero.
|
// HasDependencies reports whether any preflight counter is non-zero.
|
||||||
@@ -180,14 +180,14 @@ const (
|
|||||||
type IssuerType string
|
type IssuerType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IssuerTypeACME IssuerType = "ACME"
|
IssuerTypeACME IssuerType = "ACME"
|
||||||
IssuerTypeGenericCA IssuerType = "GenericCA"
|
IssuerTypeGenericCA IssuerType = "GenericCA"
|
||||||
IssuerTypeStepCA IssuerType = "StepCA"
|
IssuerTypeStepCA IssuerType = "StepCA"
|
||||||
IssuerTypeOpenSSL IssuerType = "OpenSSL"
|
IssuerTypeOpenSSL IssuerType = "OpenSSL"
|
||||||
IssuerTypeVault IssuerType = "VaultPKI"
|
IssuerTypeVault IssuerType = "VaultPKI"
|
||||||
IssuerTypeDigiCert IssuerType = "DigiCert"
|
IssuerTypeDigiCert IssuerType = "DigiCert"
|
||||||
IssuerTypeSectigo IssuerType = "Sectigo"
|
IssuerTypeSectigo IssuerType = "Sectigo"
|
||||||
IssuerTypeGoogleCAS IssuerType = "GoogleCAS"
|
IssuerTypeGoogleCAS IssuerType = "GoogleCAS"
|
||||||
IssuerTypeAWSACMPCA IssuerType = "AWSACMPCA"
|
IssuerTypeAWSACMPCA IssuerType = "AWSACMPCA"
|
||||||
IssuerTypeEntrust IssuerType = "Entrust"
|
IssuerTypeEntrust IssuerType = "Entrust"
|
||||||
IssuerTypeGlobalSign IssuerType = "GlobalSign"
|
IssuerTypeGlobalSign IssuerType = "GlobalSign"
|
||||||
@@ -198,16 +198,16 @@ const (
|
|||||||
type TargetType string
|
type TargetType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TargetTypeNGINX TargetType = "NGINX"
|
TargetTypeNGINX TargetType = "NGINX"
|
||||||
TargetTypeApache TargetType = "Apache"
|
TargetTypeApache TargetType = "Apache"
|
||||||
TargetTypeHAProxy TargetType = "HAProxy"
|
TargetTypeHAProxy TargetType = "HAProxy"
|
||||||
TargetTypeF5 TargetType = "F5"
|
TargetTypeF5 TargetType = "F5"
|
||||||
TargetTypeIIS TargetType = "IIS"
|
TargetTypeIIS TargetType = "IIS"
|
||||||
TargetTypeTraefik TargetType = "Traefik"
|
TargetTypeTraefik TargetType = "Traefik"
|
||||||
TargetTypeCaddy TargetType = "Caddy"
|
TargetTypeCaddy TargetType = "Caddy"
|
||||||
TargetTypeEnvoy TargetType = "Envoy"
|
TargetTypeEnvoy TargetType = "Envoy"
|
||||||
TargetTypePostfix TargetType = "Postfix"
|
TargetTypePostfix TargetType = "Postfix"
|
||||||
TargetTypeDovecot TargetType = "Dovecot"
|
TargetTypeDovecot TargetType = "Dovecot"
|
||||||
TargetTypeSSH TargetType = "SSH"
|
TargetTypeSSH TargetType = "SSH"
|
||||||
TargetTypeWinCertStore TargetType = "WinCertStore"
|
TargetTypeWinCertStore TargetType = "WinCertStore"
|
||||||
TargetTypeJavaKeystore TargetType = "JavaKeystore"
|
TargetTypeJavaKeystore TargetType = "JavaKeystore"
|
||||||
|
|||||||
@@ -24,34 +24,34 @@ func IsValidHealthStatus(s string) bool {
|
|||||||
|
|
||||||
// EndpointHealthCheck represents a monitored TLS endpoint.
|
// EndpointHealthCheck represents a monitored TLS endpoint.
|
||||||
type EndpointHealthCheck struct {
|
type EndpointHealthCheck struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Endpoint string `json:"endpoint"`
|
Endpoint string `json:"endpoint"`
|
||||||
CertificateID *string `json:"certificate_id,omitempty"`
|
CertificateID *string `json:"certificate_id,omitempty"`
|
||||||
NetworkScanTargetID *string `json:"network_scan_target_id,omitempty"`
|
NetworkScanTargetID *string `json:"network_scan_target_id,omitempty"`
|
||||||
ExpectedFingerprint string `json:"expected_fingerprint"`
|
ExpectedFingerprint string `json:"expected_fingerprint"`
|
||||||
ObservedFingerprint string `json:"observed_fingerprint"`
|
ObservedFingerprint string `json:"observed_fingerprint"`
|
||||||
Status HealthStatus `json:"status"`
|
Status HealthStatus `json:"status"`
|
||||||
ConsecutiveFailures int `json:"consecutive_failures"`
|
ConsecutiveFailures int `json:"consecutive_failures"`
|
||||||
ResponseTimeMs int `json:"response_time_ms"`
|
ResponseTimeMs int `json:"response_time_ms"`
|
||||||
TLSVersion string `json:"tls_version"`
|
TLSVersion string `json:"tls_version"`
|
||||||
CipherSuite string `json:"cipher_suite"`
|
CipherSuite string `json:"cipher_suite"`
|
||||||
CertSubject string `json:"cert_subject"`
|
CertSubject string `json:"cert_subject"`
|
||||||
CertIssuer string `json:"cert_issuer"`
|
CertIssuer string `json:"cert_issuer"`
|
||||||
CertExpiry *time.Time `json:"cert_expiry,omitempty"`
|
CertExpiry *time.Time `json:"cert_expiry,omitempty"`
|
||||||
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
|
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
|
||||||
LastSuccessAt *time.Time `json:"last_success_at,omitempty"`
|
LastSuccessAt *time.Time `json:"last_success_at,omitempty"`
|
||||||
LastFailureAt *time.Time `json:"last_failure_at,omitempty"`
|
LastFailureAt *time.Time `json:"last_failure_at,omitempty"`
|
||||||
LastTransitionAt *time.Time `json:"last_transition_at,omitempty"`
|
LastTransitionAt *time.Time `json:"last_transition_at,omitempty"`
|
||||||
FailureReason string `json:"failure_reason"`
|
FailureReason string `json:"failure_reason"`
|
||||||
DegradedThreshold int `json:"degraded_threshold"`
|
DegradedThreshold int `json:"degraded_threshold"`
|
||||||
DownThreshold int `json:"down_threshold"`
|
DownThreshold int `json:"down_threshold"`
|
||||||
CheckIntervalSecs int `json:"check_interval_seconds"`
|
CheckIntervalSecs int `json:"check_interval_seconds"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Acknowledged bool `json:"acknowledged"`
|
Acknowledged bool `json:"acknowledged"`
|
||||||
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
|
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
|
||||||
AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"`
|
AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransitionStatus computes the new health status based on the probe result.
|
// TransitionStatus computes the new health status based on the probe result.
|
||||||
@@ -89,13 +89,13 @@ func (h *EndpointHealthCheck) TransitionStatus(probeSuccess bool, observedFinger
|
|||||||
|
|
||||||
// HealthHistoryEntry represents a single probe record.
|
// HealthHistoryEntry represents a single probe record.
|
||||||
type HealthHistoryEntry struct {
|
type HealthHistoryEntry struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
HealthCheckID string `json:"health_check_id"`
|
HealthCheckID string `json:"health_check_id"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
ResponseTimeMs int `json:"response_time_ms"`
|
ResponseTimeMs int `json:"response_time_ms"`
|
||||||
Fingerprint string `json:"fingerprint"`
|
Fingerprint string `json:"fingerprint"`
|
||||||
FailureReason string `json:"failure_reason"`
|
FailureReason string `json:"failure_reason"`
|
||||||
CheckedAt time.Time `json:"checked_at"`
|
CheckedAt time.Time `json:"checked_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthCheckSummary contains aggregate counts by status.
|
// HealthCheckSummary contains aggregate counts by status.
|
||||||
|
|||||||
+17
-17
@@ -7,23 +7,23 @@ import (
|
|||||||
|
|
||||||
// Job represents a unit of work in the certificate control plane.
|
// Job represents a unit of work in the certificate control plane.
|
||||||
type Job struct {
|
type Job struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type JobType `json:"type"`
|
Type JobType `json:"type"`
|
||||||
CertificateID string `json:"certificate_id"`
|
CertificateID string `json:"certificate_id"`
|
||||||
TargetID *string `json:"target_id,omitempty"`
|
TargetID *string `json:"target_id,omitempty"`
|
||||||
AgentID *string `json:"agent_id,omitempty"`
|
AgentID *string `json:"agent_id,omitempty"`
|
||||||
Status JobStatus `json:"status"`
|
Status JobStatus `json:"status"`
|
||||||
Attempts int `json:"attempts"`
|
Attempts int `json:"attempts"`
|
||||||
MaxAttempts int `json:"max_attempts"`
|
MaxAttempts int `json:"max_attempts"`
|
||||||
LastError *string `json:"last_error,omitempty"`
|
LastError *string `json:"last_error,omitempty"`
|
||||||
ScheduledAt time.Time `json:"scheduled_at"`
|
ScheduledAt time.Time `json:"scheduled_at"`
|
||||||
StartedAt *time.Time `json:"started_at,omitempty"`
|
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
VerificationStatus VerificationStatus `json:"verification_status"`
|
VerificationStatus VerificationStatus `json:"verification_status"`
|
||||||
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
||||||
VerificationError *string `json:"verification_error,omitempty"`
|
VerificationError *string `json:"verification_error,omitempty"`
|
||||||
VerificationFp *string `json:"verification_fingerprint,omitempty"`
|
VerificationFp *string `json:"verification_fingerprint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobType represents the classification of work to be performed.
|
// JobType represents the classification of work to be performed.
|
||||||
|
|||||||
@@ -104,15 +104,15 @@ func TestNotificationEvent_RetryFields(t *testing.T) {
|
|||||||
next := time.Now().Add(2 * time.Minute)
|
next := time.Now().Add(2 * time.Minute)
|
||||||
lastErr := "connection refused"
|
lastErr := "connection refused"
|
||||||
event := &NotificationEvent{
|
event := &NotificationEvent{
|
||||||
ID: "notif-retry-001",
|
ID: "notif-retry-001",
|
||||||
Type: NotificationTypeExpirationWarning,
|
Type: NotificationTypeExpirationWarning,
|
||||||
Channel: NotificationChannelWebhook,
|
Channel: NotificationChannelWebhook,
|
||||||
Recipient: "https://hooks.example.com/certs",
|
Recipient: "https://hooks.example.com/certs",
|
||||||
Message: "retry me",
|
Message: "retry me",
|
||||||
Status: string(NotificationStatusFailed),
|
Status: string(NotificationStatusFailed),
|
||||||
RetryCount: 3,
|
RetryCount: 3,
|
||||||
NextRetryAt: &next,
|
NextRetryAt: &next,
|
||||||
LastError: &lastErr,
|
LastError: &lastErr,
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.RetryCount != 3 {
|
if event.RetryCount != 3 {
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import "time"
|
|||||||
type OCSPResponseCacheEntry struct {
|
type OCSPResponseCacheEntry struct {
|
||||||
IssuerID string `json:"issuer_id"`
|
IssuerID string `json:"issuer_id"`
|
||||||
SerialHex string `json:"serial_hex"`
|
SerialHex string `json:"serial_hex"`
|
||||||
ResponseDER []byte `json:"-"` // raw DER, omitted from admin JSON to keep responses lean
|
ResponseDER []byte `json:"-"` // raw DER, omitted from admin JSON to keep responses lean
|
||||||
CertStatus string `json:"cert_status"` // "good" | "revoked" | "unknown"
|
CertStatus string `json:"cert_status"` // "good" | "revoked" | "unknown"
|
||||||
RevocationReason int `json:"revocation_reason,omitempty"` // only set when CertStatus == "revoked"
|
RevocationReason int `json:"revocation_reason,omitempty"` // only set when CertStatus == "revoked"
|
||||||
RevokedAt time.Time `json:"revoked_at,omitempty"` // only set when CertStatus == "revoked"
|
RevokedAt time.Time `json:"revoked_at,omitempty"` // only set when CertStatus == "revoked"
|
||||||
ThisUpdate time.Time `json:"this_update"`
|
ThisUpdate time.Time `json:"this_update"`
|
||||||
NextUpdate time.Time `json:"next_update"`
|
NextUpdate time.Time `json:"next_update"`
|
||||||
GeneratedAt time.Time `json:"generated_at"`
|
GeneratedAt time.Time `json:"generated_at"`
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ type PolicyRule struct {
|
|||||||
type PolicyType string
|
type PolicyType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PolicyTypeAllowedIssuers PolicyType = "AllowedIssuers"
|
PolicyTypeAllowedIssuers PolicyType = "AllowedIssuers"
|
||||||
PolicyTypeAllowedDomains PolicyType = "AllowedDomains"
|
PolicyTypeAllowedDomains PolicyType = "AllowedDomains"
|
||||||
PolicyTypeRequiredMetadata PolicyType = "RequiredMetadata"
|
PolicyTypeRequiredMetadata PolicyType = "RequiredMetadata"
|
||||||
PolicyTypeAllowedEnvironments PolicyType = "AllowedEnvironments"
|
PolicyTypeAllowedEnvironments PolicyType = "AllowedEnvironments"
|
||||||
PolicyTypeRenewalLeadTime PolicyType = "RenewalLeadTime"
|
PolicyTypeRenewalLeadTime PolicyType = "RenewalLeadTime"
|
||||||
PolicyTypeCertificateLifetime PolicyType = "CertificateLifetime"
|
PolicyTypeCertificateLifetime PolicyType = "CertificateLifetime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PolicyViolation records an instance of a certificate violating a policy rule.
|
// PolicyViolation records an instance of a certificate violating a policy rule.
|
||||||
|
|||||||
@@ -704,17 +704,17 @@ func TestM20EnhancedQueryAPI(t *testing.T) {
|
|||||||
// Setup: Create a certificate for testing
|
// Setup: Create a certificate for testing
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
cert := &domain.ManagedCertificate{
|
cert := &domain.ManagedCertificate{
|
||||||
ID: "mc-m20-test-1",
|
ID: "mc-m20-test-1",
|
||||||
Name: "M20 Test Cert",
|
Name: "M20 Test Cert",
|
||||||
CommonName: "m20.example.com",
|
CommonName: "m20.example.com",
|
||||||
Environment: "production",
|
Environment: "production",
|
||||||
Status: domain.CertificateStatusActive,
|
Status: domain.CertificateStatusActive,
|
||||||
IssuerID: "iss-local",
|
IssuerID: "iss-local",
|
||||||
OwnerID: "owner-ops",
|
OwnerID: "owner-ops",
|
||||||
TeamID: "team-platform",
|
TeamID: "team-platform",
|
||||||
CertificateProfileID: "prof-standard",
|
CertificateProfileID: "prof-standard",
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
certRepo.certs["mc-m20-test-1"] = cert
|
certRepo.certs["mc-m20-test-1"] = cert
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/shankar0123/certctl/internal/domain"
|
"github.com/shankar0123/certctl/internal/domain"
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ func TestWrapPingError_AuthFailureGuidance(t *testing.T) {
|
|||||||
|
|
||||||
// Contract elements — the operator-facing string is what we ship.
|
// Contract elements — the operator-facing string is what we ship.
|
||||||
wantSubstrings := []string{
|
wantSubstrings := []string{
|
||||||
"SQLSTATE 28P01", // operators grep on this
|
"SQLSTATE 28P01", // operators grep on this
|
||||||
"POSTGRES_PASSWORD", // names the variable that traps
|
"POSTGRES_PASSWORD", // names the variable that traps
|
||||||
"first boot", // the mechanism in plain language
|
"first boot", // the mechanism in plain language
|
||||||
"down -v", // destructive remediation
|
"down -v", // destructive remediation
|
||||||
"ALTER ROLE", // non-destructive remediation
|
"ALTER ROLE", // non-destructive remediation
|
||||||
}
|
}
|
||||||
for _, s := range wantSubstrings {
|
for _, s := range wantSubstrings {
|
||||||
if !strings.Contains(got, s) {
|
if !strings.Contains(got, s) {
|
||||||
|
|||||||
@@ -235,8 +235,8 @@ func TestHealthCheckRepository_ListDueForCheck(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
now := time.Now().UTC().Truncate(time.Microsecond)
|
now := time.Now().UTC().Truncate(time.Microsecond)
|
||||||
pastDue := now.Add(-10 * time.Minute) // > 300s ago, enabled → due
|
pastDue := now.Add(-10 * time.Minute) // > 300s ago, enabled → due
|
||||||
recent := now.Add(-30 * time.Second) // < 300s ago, enabled → not due
|
recent := now.Add(-30 * time.Second) // < 300s ago, enabled → not due
|
||||||
|
|
||||||
// (a) enabled + null last_checked_at — NULLS FIRST puts this first
|
// (a) enabled + null last_checked_at — NULLS FIRST puts this first
|
||||||
a := newHealthCheck("hc-due-a", "a.example.com:443", domain.HealthStatusUnknown, true)
|
a := newHealthCheck("hc-due-a", "a.example.com:443", domain.HealthStatusUnknown, true)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/shankar0123/certctl/internal/domain"
|
"github.com/shankar0123/certctl/internal/domain"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/shankar0123/certctl/internal/domain"
|
"github.com/shankar0123/certctl/internal/domain"
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ func TestCertificateRepository_GetExpiringCertificates(t *testing.T) {
|
|||||||
ID: tc.id, Name: tc.id, CommonName: tc.id + ".example.com",
|
ID: tc.id, Name: tc.id, CommonName: tc.id + ".example.com",
|
||||||
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
||||||
IssuerID: issuerID, RenewalPolicyID: policyID,
|
IssuerID: issuerID, RenewalPolicyID: policyID,
|
||||||
Status: domain.CertificateStatusActive,
|
Status: domain.CertificateStatusActive,
|
||||||
ExpiresAt: tc.expires, Tags: map[string]string{},
|
ExpiresAt: tc.expires, Tags: map[string]string{},
|
||||||
CreatedAt: now, UpdatedAt: now,
|
CreatedAt: now, UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -780,7 +780,7 @@ func TestJobRepository_CRUD(t *testing.T) {
|
|||||||
ID: "mc-job-test", Name: "job-test", CommonName: "job.example.com",
|
ID: "mc-job-test", Name: "job-test", CommonName: "job.example.com",
|
||||||
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
||||||
IssuerID: issuerID, RenewalPolicyID: policyID,
|
IssuerID: issuerID, RenewalPolicyID: policyID,
|
||||||
Status: domain.CertificateStatusActive,
|
Status: domain.CertificateStatusActive,
|
||||||
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
|
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
|
||||||
CreatedAt: now, UpdatedAt: now,
|
CreatedAt: now, UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -871,7 +871,7 @@ func TestRevocationRepository_CRUD(t *testing.T) {
|
|||||||
ID: "mc-rev-test", Name: "rev-test", CommonName: "rev.example.com",
|
ID: "mc-rev-test", Name: "rev-test", CommonName: "rev.example.com",
|
||||||
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
||||||
IssuerID: issuerID, RenewalPolicyID: policyID,
|
IssuerID: issuerID, RenewalPolicyID: policyID,
|
||||||
Status: domain.CertificateStatusRevoked,
|
Status: domain.CertificateStatusRevoked,
|
||||||
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
|
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
|
||||||
CreatedAt: now, UpdatedAt: now,
|
CreatedAt: now, UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -1250,12 +1250,12 @@ func TestProfileRepository_CRUD(t *testing.T) {
|
|||||||
{Algorithm: "RSA", MinSize: 2048},
|
{Algorithm: "RSA", MinSize: 2048},
|
||||||
{Algorithm: "ECDSA", MinSize: 256},
|
{Algorithm: "ECDSA", MinSize: 256},
|
||||||
},
|
},
|
||||||
MaxTTLSeconds: 86400,
|
MaxTTLSeconds: 86400,
|
||||||
AllowedEKUs: []string{"serverAuth"},
|
AllowedEKUs: []string{"serverAuth"},
|
||||||
AllowShortLived: false,
|
AllowShortLived: false,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.Create(ctx, profile); err != nil {
|
if err := repo.Create(ctx, profile); err != nil {
|
||||||
@@ -1306,7 +1306,7 @@ func TestNotificationRepository_CRUD(t *testing.T) {
|
|||||||
ID: "mc-notif-test", Name: "notif-test", CommonName: "notif.example.com",
|
ID: "mc-notif-test", Name: "notif-test", CommonName: "notif.example.com",
|
||||||
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
||||||
IssuerID: issuerID, RenewalPolicyID: policyID,
|
IssuerID: issuerID, RenewalPolicyID: policyID,
|
||||||
Status: domain.CertificateStatusActive,
|
Status: domain.CertificateStatusActive,
|
||||||
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
|
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
|
||||||
CreatedAt: now, UpdatedAt: now,
|
CreatedAt: now, UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -1432,7 +1432,7 @@ func TestDiscoveryRepository_DiscoveredCertCRUD(t *testing.T) {
|
|||||||
ID: "mc-linked-cert", Name: "linked-cert", CommonName: "linked.example.com",
|
ID: "mc-linked-cert", Name: "linked-cert", CommonName: "linked.example.com",
|
||||||
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
|
||||||
IssuerID: issuerID, RenewalPolicyID: policyID,
|
IssuerID: issuerID, RenewalPolicyID: policyID,
|
||||||
Status: domain.CertificateStatusActive,
|
Status: domain.CertificateStatusActive,
|
||||||
ExpiresAt: now.Add(90 * 24 * time.Hour), Tags: map[string]string{},
|
ExpiresAt: now.Add(90 * 24 * time.Hour), Tags: map[string]string{},
|
||||||
CreatedAt: now, UpdatedAt: now,
|
CreatedAt: now, UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -1447,7 +1447,7 @@ func TestDiscoveryRepository_DiscoveredCertCRUD(t *testing.T) {
|
|||||||
NotBefore: ¬Before, NotAfter: ¬After, KeyAlgorithm: "RSA", KeySize: 2048,
|
NotBefore: ¬Before, NotAfter: ¬After, KeyAlgorithm: "RSA", KeySize: 2048,
|
||||||
IsCA: false, PEMData: "---PEM---", SourcePath: "/etc/ssl/certs/disc.pem",
|
IsCA: false, PEMData: "---PEM---", SourcePath: "/etc/ssl/certs/disc.pem",
|
||||||
SourceFormat: "PEM", AgentID: "agent-dcert-test",
|
SourceFormat: "PEM", AgentID: "agent-dcert-test",
|
||||||
Status: domain.DiscoveryStatusUnmanaged,
|
Status: domain.DiscoveryStatusUnmanaged,
|
||||||
FirstSeenAt: now, LastSeenAt: now, CreatedAt: now, UpdatedAt: now,
|
FirstSeenAt: now, LastSeenAt: now, CreatedAt: now, UpdatedAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1577,8 +1577,8 @@ func TestNetworkScanRepository_CRUD(t *testing.T) {
|
|||||||
|
|
||||||
target := &domain.NetworkScanTarget{
|
target := &domain.NetworkScanTarget{
|
||||||
ID: "ns-test-1", Name: "Internal Network",
|
ID: "ns-test-1", Name: "Internal Network",
|
||||||
CIDRs: []string{"10.0.0.0/24", "192.168.1.0/24"},
|
CIDRs: []string{"10.0.0.0/24", "192.168.1.0/24"},
|
||||||
Ports: []int64{443, 8443},
|
Ports: []int64{443, 8443},
|
||||||
Enabled: true, ScanIntervalHours: 6, TimeoutMs: 5000,
|
Enabled: true, ScanIntervalHours: 6, TimeoutMs: 5000,
|
||||||
CreatedAt: now, UpdatedAt: now,
|
CreatedAt: now, UpdatedAt: now,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
|
|
||||||
"github.com/shankar0123/certctl/internal/domain"
|
"github.com/shankar0123/certctl/internal/domain"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/shankar0123/certctl/internal/domain"
|
"github.com/shankar0123/certctl/internal/domain"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/shankar0123/certctl/internal/domain"
|
"github.com/shankar0123/certctl/internal/domain"
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ import (
|
|||||||
|
|
||||||
// mockRenewalService is a mock implementation for testing.
|
// mockRenewalService is a mock implementation for testing.
|
||||||
type mockRenewalService struct {
|
type mockRenewalService struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
callCount int
|
callCount int
|
||||||
callTimes []time.Time
|
callTimes []time.Time
|
||||||
expireCallCount int
|
expireCallCount int
|
||||||
expireCallTimes []time.Time
|
expireCallTimes []time.Time
|
||||||
slowDelay time.Duration
|
slowDelay time.Duration
|
||||||
shouldError bool
|
shouldError bool
|
||||||
blockCh chan struct{} // if non-nil, blocks until closed (ignores context)
|
blockCh chan struct{} // if non-nil, blocks until closed (ignores context)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockRenewalService) CheckExpiringCertificates(ctx context.Context) error {
|
func (m *mockRenewalService) CheckExpiringCertificates(ctx context.Context) error {
|
||||||
@@ -138,7 +138,6 @@ func (m *mockJobService) RetryFailedJobs(ctx context.Context, maxRetries int) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ReapTimedOutJobs is the scheduler-driven counterpart to ProcessPendingJobs that
|
// ReapTimedOutJobs is the scheduler-driven counterpart to ProcessPendingJobs that
|
||||||
// covers coverage gap I-003: JobService.ReapTimedOutJobs (via JobReaperService interface)
|
// covers coverage gap I-003: JobService.ReapTimedOutJobs (via JobReaperService interface)
|
||||||
// had no runtime caller prior to the jobTimeoutLoop being wired.
|
// had no runtime caller prior to the jobTimeoutLoop being wired.
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ import (
|
|||||||
|
|
||||||
// mockAgentGroupRepo is a test implementation of AgentGroupRepository
|
// mockAgentGroupRepo is a test implementation of AgentGroupRepository
|
||||||
type mockAgentGroupRepo struct {
|
type mockAgentGroupRepo struct {
|
||||||
groups map[string]*domain.AgentGroup
|
groups map[string]*domain.AgentGroup
|
||||||
members map[string][]*domain.Agent
|
members map[string][]*domain.Agent
|
||||||
CreateErr error
|
CreateErr error
|
||||||
UpdateErr error
|
UpdateErr error
|
||||||
DeleteErr error
|
DeleteErr error
|
||||||
GetErr error
|
GetErr error
|
||||||
ListErr error
|
ListErr error
|
||||||
ListMembersErr error
|
ListMembersErr error
|
||||||
AddMemberErr error
|
AddMemberErr error
|
||||||
RemoveMemberErr error
|
RemoveMemberErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockAgentGroupRepository() *mockAgentGroupRepo {
|
func newMockAgentGroupRepository() *mockAgentGroupRepo {
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ func TestAgentService_UpdateJobStatus_DelegatesToReportJobStatus(t *testing.T) {
|
|||||||
svc, repo, _, jobRepo, _ := newTestAgentSvc(t)
|
svc, repo, _, jobRepo, _ := newTestAgentSvc(t)
|
||||||
repo.Agents["a-1"] = &domain.Agent{ID: "a-1", Status: domain.AgentStatusOnline}
|
repo.Agents["a-1"] = &domain.Agent{ID: "a-1", Status: domain.AgentStatusOnline}
|
||||||
jobRepo.Jobs["j-1"] = &domain.Job{
|
jobRepo.Jobs["j-1"] = &domain.Job{
|
||||||
ID: "j-1",
|
ID: "j-1",
|
||||||
AgentID: strPtr("a-1"),
|
AgentID: strPtr("a-1"),
|
||||||
Status: domain.JobStatusRunning,
|
Status: domain.JobStatusRunning,
|
||||||
}
|
}
|
||||||
err := svc.UpdateJobStatus(context.Background(), "a-1", "j-1", "Completed", "")
|
err := svc.UpdateJobStatus(context.Background(), "a-1", "j-1", "Completed", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/shankar0123/certctl/internal/domain"
|
"github.com/shankar0123/certctl/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func TestRegisterAgent(t *testing.T) {
|
func TestRegisterAgent(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
agentRepo := &mockAgentRepo{
|
agentRepo := &mockAgentRepo{
|
||||||
|
|||||||
@@ -73,29 +73,29 @@ var credentialKeys = map[string]bool{
|
|||||||
// Note `ip_address` is debatable — useful for forensics but flagged by
|
// Note `ip_address` is debatable — useful for forensics but flagged by
|
||||||
// GDPR Art. 32 — defaulting to redact, operators can audit + adjust.
|
// GDPR Art. 32 — defaulting to redact, operators can audit + adjust.
|
||||||
var piiKeys = map[string]bool{
|
var piiKeys = map[string]bool{
|
||||||
"email": true,
|
"email": true,
|
||||||
"email_address": true,
|
"email_address": true,
|
||||||
"phone": true,
|
"phone": true,
|
||||||
"phone_number": true,
|
"phone_number": true,
|
||||||
"telephone": true,
|
"telephone": true,
|
||||||
"ssn": true,
|
"ssn": true,
|
||||||
"social_security": true,
|
"social_security": true,
|
||||||
"dob": true,
|
"dob": true,
|
||||||
"date_of_birth": true,
|
"date_of_birth": true,
|
||||||
"name": true,
|
"name": true,
|
||||||
"full_name": true,
|
"full_name": true,
|
||||||
"first_name": true,
|
"first_name": true,
|
||||||
"last_name": true,
|
"last_name": true,
|
||||||
"surname": true,
|
"surname": true,
|
||||||
"address": true,
|
"address": true,
|
||||||
"street": true,
|
"street": true,
|
||||||
"street_address": true,
|
"street_address": true,
|
||||||
"city": true,
|
"city": true,
|
||||||
"postal_code": true,
|
"postal_code": true,
|
||||||
"zip": true,
|
"zip": true,
|
||||||
"zipcode": true,
|
"zipcode": true,
|
||||||
"ip": true,
|
"ip": true,
|
||||||
"ip_address": true,
|
"ip_address": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedactDetailsForAudit walks a details map and returns a NEW map with
|
// RedactDetailsForAudit walks a details map and returns a NEW map with
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ func TestRedactDetailsForAudit_CredentialKeys(t *testing.T) {
|
|||||||
for _, key := range cases {
|
for _, key := range cases {
|
||||||
t.Run(key, func(t *testing.T) {
|
t.Run(key, func(t *testing.T) {
|
||||||
in := map[string]interface{}{
|
in := map[string]interface{}{
|
||||||
key: "sensitive-value-do-not-leak",
|
key: "sensitive-value-do-not-leak",
|
||||||
"non_sensitive_id": "ok-public-id",
|
"non_sensitive_id": "ok-public-id",
|
||||||
}
|
}
|
||||||
out := RedactDetailsForAudit(in)
|
out := RedactDetailsForAudit(in)
|
||||||
if out[key] != "[REDACTED:CREDENTIAL]" {
|
if out[key] != "[REDACTED:CREDENTIAL]" {
|
||||||
@@ -70,7 +70,7 @@ func TestRedactDetailsForAudit_NestedMap(t *testing.T) {
|
|||||||
in := map[string]interface{}{
|
in := map[string]interface{}{
|
||||||
"resource_id": "iss-prod",
|
"resource_id": "iss-prod",
|
||||||
"config": map[string]interface{}{
|
"config": map[string]interface{}{
|
||||||
"endpoint": "https://acme.example.com",
|
"endpoint": "https://acme.example.com",
|
||||||
"eab_secret": "do-not-leak-this-secret",
|
"eab_secret": "do-not-leak-this-secret",
|
||||||
"contact": map[string]interface{}{
|
"contact": map[string]interface{}{
|
||||||
"email": "ops@example.com",
|
"email": "ops@example.com",
|
||||||
@@ -159,8 +159,8 @@ func TestRedactDetailsForAudit_NoRedactionPath(t *testing.T) {
|
|||||||
// Maps with no sensitive keys should NOT have a redacted_keys array
|
// Maps with no sensitive keys should NOT have a redacted_keys array
|
||||||
// — clutter-free for the common case.
|
// — clutter-free for the common case.
|
||||||
in := map[string]interface{}{
|
in := map[string]interface{}{
|
||||||
"action": "create_certificate",
|
"action": "create_certificate",
|
||||||
"cert_id": "mc-prod-001",
|
"cert_id": "mc-prod-001",
|
||||||
"latency_ms": float64(42),
|
"latency_ms": float64(42),
|
||||||
}
|
}
|
||||||
out := RedactDetailsForAudit(in)
|
out := RedactDetailsForAudit(in)
|
||||||
@@ -171,7 +171,7 @@ func TestRedactDetailsForAudit_NoRedactionPath(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedactDetailsForAudit_DoesNotMutateInput(t *testing.T) {
|
func TestRedactDetailsForAudit_DoesNotMutateInput(t *testing.T) {
|
||||||
in := map[string]interface{}{
|
in := map[string]interface{}{
|
||||||
"api_key": "secret-do-not-leak",
|
"api_key": "secret-do-not-leak",
|
||||||
"resource": "iss-prod",
|
"resource": "iss-prod",
|
||||||
}
|
}
|
||||||
_ = RedactDetailsForAudit(in)
|
_ = RedactDetailsForAudit(in)
|
||||||
@@ -197,8 +197,8 @@ func TestRedactDetailsForAudit_JSONRoundTrip(t *testing.T) {
|
|||||||
// The redacted map MUST round-trip through json.Marshal (the
|
// The redacted map MUST round-trip through json.Marshal (the
|
||||||
// AuditService persistence path). Catches type-assertion regressions.
|
// AuditService persistence path). Catches type-assertion regressions.
|
||||||
in := map[string]interface{}{
|
in := map[string]interface{}{
|
||||||
"reason": "compromised-key",
|
"reason": "compromised-key",
|
||||||
"api_key": "leak-me",
|
"api_key": "leak-me",
|
||||||
"contacts": []interface{}{
|
"contacts": []interface{}{
|
||||||
map[string]interface{}{"email": "ops@example.com"},
|
map[string]interface{}{"email": "ops@example.com"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ func TestBulkReassign_HappyPath(t *testing.T) {
|
|||||||
func TestBulkReassign_SkipsAlreadyOwned(t *testing.T) {
|
func TestBulkReassign_SkipsAlreadyOwned(t *testing.T) {
|
||||||
svc, certRepo, ownerRepo, _ := newBulkReassignmentTestService()
|
svc, certRepo, ownerRepo, _ := newBulkReassignmentTestService()
|
||||||
addOwner(ownerRepo, "o-bob")
|
addOwner(ownerRepo, "o-bob")
|
||||||
addOwnedCert(certRepo, "mc-1", "o-bob", "") // already owned by target
|
addOwnedCert(certRepo, "mc-1", "o-bob", "") // already owned by target
|
||||||
addOwnedCert(certRepo, "mc-2", "o-alice", "") // needs reassign
|
addOwnedCert(certRepo, "mc-2", "o-alice", "") // needs reassign
|
||||||
|
|
||||||
res, err := svc.BulkReassign(context.Background(),
|
res, err := svc.BulkReassign(context.Background(),
|
||||||
domain.BulkReassignmentRequest{
|
domain.BulkReassignmentRequest{
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ func newBulkRevocationTestService() (*BulkRevocationService, *mockCertRepo, *moc
|
|||||||
|
|
||||||
func addTestCert(repo *mockCertRepo, id, status, issuerID string) {
|
func addTestCert(repo *mockCertRepo, id, status, issuerID string) {
|
||||||
cert := &domain.ManagedCertificate{
|
cert := &domain.ManagedCertificate{
|
||||||
ID: id,
|
ID: id,
|
||||||
CommonName: id + ".example.com",
|
CommonName: id + ".example.com",
|
||||||
Status: domain.CertificateStatus(status),
|
Status: domain.CertificateStatus(status),
|
||||||
IssuerID: issuerID,
|
IssuerID: issuerID,
|
||||||
ExpiresAt: time.Now().AddDate(0, 6, 0),
|
ExpiresAt: time.Now().AddDate(0, 6, 0),
|
||||||
}
|
}
|
||||||
repo.AddCert(cert)
|
repo.AddCert(cert)
|
||||||
// Add a version with serial number (needed by RevokeCertificateWithActor)
|
// Add a version with serial number (needed by RevokeCertificateWithActor)
|
||||||
@@ -54,13 +54,13 @@ func addTestCert(repo *mockCertRepo, id, status, issuerID string) {
|
|||||||
|
|
||||||
func addTestCertWithProfile(repo *mockCertRepo, id, status, issuerID, profileID, ownerID string) {
|
func addTestCertWithProfile(repo *mockCertRepo, id, status, issuerID, profileID, ownerID string) {
|
||||||
cert := &domain.ManagedCertificate{
|
cert := &domain.ManagedCertificate{
|
||||||
ID: id,
|
ID: id,
|
||||||
CommonName: id + ".example.com",
|
CommonName: id + ".example.com",
|
||||||
Status: domain.CertificateStatus(status),
|
Status: domain.CertificateStatus(status),
|
||||||
IssuerID: issuerID,
|
IssuerID: issuerID,
|
||||||
CertificateProfileID: profileID,
|
CertificateProfileID: profileID,
|
||||||
OwnerID: ownerID,
|
OwnerID: ownerID,
|
||||||
ExpiresAt: time.Now().AddDate(0, 6, 0),
|
ExpiresAt: time.Now().AddDate(0, 6, 0),
|
||||||
}
|
}
|
||||||
repo.AddCert(cert)
|
repo.AddCert(cert)
|
||||||
repo.Versions[id] = []*domain.CertificateVersion{
|
repo.Versions[id] = []*domain.CertificateVersion{
|
||||||
@@ -272,11 +272,11 @@ func TestBulkRevoke_PartialFailure(t *testing.T) {
|
|||||||
addTestCert(certRepo, "mc-1", "Active", "iss-local")
|
addTestCert(certRepo, "mc-1", "Active", "iss-local")
|
||||||
// mc-2 is active but has NO version — RevokeCertificateWithActor will fail on GetLatestVersion
|
// mc-2 is active but has NO version — RevokeCertificateWithActor will fail on GetLatestVersion
|
||||||
cert2 := &domain.ManagedCertificate{
|
cert2 := &domain.ManagedCertificate{
|
||||||
ID: "mc-2",
|
ID: "mc-2",
|
||||||
CommonName: "mc-2.example.com",
|
CommonName: "mc-2.example.com",
|
||||||
Status: domain.CertificateStatusActive,
|
Status: domain.CertificateStatusActive,
|
||||||
IssuerID: "iss-local",
|
IssuerID: "iss-local",
|
||||||
ExpiresAt: time.Now().AddDate(0, 6, 0),
|
ExpiresAt: time.Now().AddDate(0, 6, 0),
|
||||||
}
|
}
|
||||||
certRepo.AddCert(cert2)
|
certRepo.AddCert(cert2)
|
||||||
// Don't add versions for mc-2 so GetLatestVersion returns errNotFound
|
// Don't add versions for mc-2 so GetLatestVersion returns errNotFound
|
||||||
|
|||||||
@@ -266,17 +266,17 @@ func TestDeploymentService_ProcessDeploymentJob_Success(t *testing.T) {
|
|||||||
// Add agent with recent heartbeat
|
// Add agent with recent heartbeat
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
agent := &domain.Agent{
|
agent := &domain.Agent{
|
||||||
ID: "agent-1",
|
ID: "agent-1",
|
||||||
Name: "Test Agent",
|
Name: "Test Agent",
|
||||||
Hostname: "agent.example.com",
|
Hostname: "agent.example.com",
|
||||||
Status: domain.AgentStatusOnline,
|
Status: domain.AgentStatusOnline,
|
||||||
LastHeartbeatAt: &now,
|
LastHeartbeatAt: &now,
|
||||||
RegisteredAt: time.Now(),
|
RegisteredAt: time.Now(),
|
||||||
APIKeyHash: "hash-1",
|
APIKeyHash: "hash-1",
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
IPAddress: "192.168.1.1",
|
IPAddress: "192.168.1.1",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
}
|
}
|
||||||
agentRepo.AddAgent(agent)
|
agentRepo.AddAgent(agent)
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -31,20 +31,20 @@ type HTMLEmailSender interface {
|
|||||||
|
|
||||||
// DigestData holds the aggregated data for a digest email.
|
// DigestData holds the aggregated data for a digest email.
|
||||||
type DigestData struct {
|
type DigestData struct {
|
||||||
GeneratedAt time.Time `json:"generated_at"`
|
GeneratedAt time.Time `json:"generated_at"`
|
||||||
TotalCertificates int64 `json:"total_certificates"`
|
TotalCertificates int64 `json:"total_certificates"`
|
||||||
ExpiringCertificates int64 `json:"expiring_certificates"`
|
ExpiringCertificates int64 `json:"expiring_certificates"`
|
||||||
ExpiredCertificates int64 `json:"expired_certificates"`
|
ExpiredCertificates int64 `json:"expired_certificates"`
|
||||||
RevokedCertificates int64 `json:"revoked_certificates"`
|
RevokedCertificates int64 `json:"revoked_certificates"`
|
||||||
ActiveAgents int64 `json:"active_agents"`
|
ActiveAgents int64 `json:"active_agents"`
|
||||||
OfflineAgents int64 `json:"offline_agents"`
|
OfflineAgents int64 `json:"offline_agents"`
|
||||||
TotalAgents int64 `json:"total_agents"`
|
TotalAgents int64 `json:"total_agents"`
|
||||||
PendingJobs int64 `json:"pending_jobs"`
|
PendingJobs int64 `json:"pending_jobs"`
|
||||||
FailedJobs int64 `json:"failed_jobs"`
|
FailedJobs int64 `json:"failed_jobs"`
|
||||||
CompletedJobs int64 `json:"completed_jobs"`
|
CompletedJobs int64 `json:"completed_jobs"`
|
||||||
ExpiringCerts []DigestCertEntry `json:"expiring_certs"`
|
ExpiringCerts []DigestCertEntry `json:"expiring_certs"`
|
||||||
RecentFailures []DigestJobEntry `json:"recent_failures"`
|
RecentFailures []DigestJobEntry `json:"recent_failures"`
|
||||||
StatusCounts []DigestStatusCount `json:"status_counts"`
|
StatusCounts []DigestStatusCount `json:"status_counts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DigestCertEntry represents a certificate entry in the digest.
|
// DigestCertEntry represents a certificate entry in the digest.
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ import (
|
|||||||
|
|
||||||
// mockDiscoveryRepo is a test implementation of DiscoveryRepository
|
// mockDiscoveryRepo is a test implementation of DiscoveryRepository
|
||||||
type mockDiscoveryRepo struct {
|
type mockDiscoveryRepo struct {
|
||||||
Scans map[string]*domain.DiscoveryScan
|
Scans map[string]*domain.DiscoveryScan
|
||||||
Discovered map[string]*domain.DiscoveredCertificate
|
Discovered map[string]*domain.DiscoveredCertificate
|
||||||
CreateScanErr error
|
CreateScanErr error
|
||||||
GetScanErr error
|
GetScanErr error
|
||||||
ListScansErr error
|
ListScansErr error
|
||||||
CreateDiscoveredErr error
|
CreateDiscoveredErr error
|
||||||
GetDiscoveredErr error
|
GetDiscoveredErr error
|
||||||
ListDiscoveredErr error
|
ListDiscoveredErr error
|
||||||
UpdateStatusErr error
|
UpdateStatusErr error
|
||||||
GetByFingerprintErr error
|
GetByFingerprintErr error
|
||||||
CountByStatusErr error
|
CountByStatusErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockDiscoveryRepository() *mockDiscoveryRepo {
|
func newMockDiscoveryRepository() *mockDiscoveryRepo {
|
||||||
@@ -268,20 +268,20 @@ func TestListDiscovered_Success(t *testing.T) {
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
cert1 := &domain.DiscoveredCertificate{
|
cert1 := &domain.DiscoveredCertificate{
|
||||||
ID: "dcert-1",
|
ID: "dcert-1",
|
||||||
AgentID: "agent-1",
|
AgentID: "agent-1",
|
||||||
CommonName: "example.com",
|
CommonName: "example.com",
|
||||||
Status: domain.DiscoveryStatusUnmanaged,
|
Status: domain.DiscoveryStatusUnmanaged,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
cert2 := &domain.DiscoveredCertificate{
|
cert2 := &domain.DiscoveredCertificate{
|
||||||
ID: "dcert-2",
|
ID: "dcert-2",
|
||||||
AgentID: "agent-1",
|
AgentID: "agent-1",
|
||||||
CommonName: "api.example.com",
|
CommonName: "api.example.com",
|
||||||
Status: domain.DiscoveryStatusManaged,
|
Status: domain.DiscoveryStatusManaged,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
discoveryRepo.Discovered[cert1.ID] = cert1
|
discoveryRepo.Discovered[cert1.ID] = cert1
|
||||||
discoveryRepo.Discovered[cert2.ID] = cert2
|
discoveryRepo.Discovered[cert2.ID] = cert2
|
||||||
@@ -304,20 +304,20 @@ func TestListDiscovered_WithStatusFilter(t *testing.T) {
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
cert1 := &domain.DiscoveredCertificate{
|
cert1 := &domain.DiscoveredCertificate{
|
||||||
ID: "dcert-1",
|
ID: "dcert-1",
|
||||||
AgentID: "agent-1",
|
AgentID: "agent-1",
|
||||||
CommonName: "example.com",
|
CommonName: "example.com",
|
||||||
Status: domain.DiscoveryStatusUnmanaged,
|
Status: domain.DiscoveryStatusUnmanaged,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
cert2 := &domain.DiscoveredCertificate{
|
cert2 := &domain.DiscoveredCertificate{
|
||||||
ID: "dcert-2",
|
ID: "dcert-2",
|
||||||
AgentID: "agent-1",
|
AgentID: "agent-1",
|
||||||
CommonName: "api.example.com",
|
CommonName: "api.example.com",
|
||||||
Status: domain.DiscoveryStatusManaged,
|
Status: domain.DiscoveryStatusManaged,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
discoveryRepo.Discovered[cert1.ID] = cert1
|
discoveryRepo.Discovered[cert1.ID] = cert1
|
||||||
discoveryRepo.Discovered[cert2.ID] = cert2
|
discoveryRepo.Discovered[cert2.ID] = cert2
|
||||||
@@ -340,11 +340,11 @@ func TestGetDiscovered_Success(t *testing.T) {
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
cert := &domain.DiscoveredCertificate{
|
cert := &domain.DiscoveredCertificate{
|
||||||
ID: "dcert-1",
|
ID: "dcert-1",
|
||||||
CommonName: "example.com",
|
CommonName: "example.com",
|
||||||
Status: domain.DiscoveryStatusUnmanaged,
|
Status: domain.DiscoveryStatusUnmanaged,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
discoveryRepo.Discovered[cert.ID] = cert
|
discoveryRepo.Discovered[cert.ID] = cert
|
||||||
|
|
||||||
@@ -363,12 +363,12 @@ func TestClaimDiscovered_Success(t *testing.T) {
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
discoveredCert := &domain.DiscoveredCertificate{
|
discoveredCert := &domain.DiscoveredCertificate{
|
||||||
ID: "dcert-1",
|
ID: "dcert-1",
|
||||||
CommonName: "example.com",
|
CommonName: "example.com",
|
||||||
FingerprintSHA256: "abc123",
|
FingerprintSHA256: "abc123",
|
||||||
Status: domain.DiscoveryStatusUnmanaged,
|
Status: domain.DiscoveryStatusUnmanaged,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
discoveryRepo.Discovered[discoveredCert.ID] = discoveredCert
|
discoveryRepo.Discovered[discoveredCert.ID] = discoveredCert
|
||||||
|
|
||||||
@@ -415,11 +415,11 @@ func TestClaimDiscovered_MissingManagedCertID(t *testing.T) {
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
cert := &domain.DiscoveredCertificate{
|
cert := &domain.DiscoveredCertificate{
|
||||||
ID: "dcert-1",
|
ID: "dcert-1",
|
||||||
CommonName: "example.com",
|
CommonName: "example.com",
|
||||||
Status: domain.DiscoveryStatusUnmanaged,
|
Status: domain.DiscoveryStatusUnmanaged,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
discoveryRepo.Discovered[cert.ID] = cert
|
discoveryRepo.Discovered[cert.ID] = cert
|
||||||
|
|
||||||
@@ -434,11 +434,11 @@ func TestClaimDiscovered_ManagedCertNotFound(t *testing.T) {
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
cert := &domain.DiscoveredCertificate{
|
cert := &domain.DiscoveredCertificate{
|
||||||
ID: "dcert-1",
|
ID: "dcert-1",
|
||||||
CommonName: "example.com",
|
CommonName: "example.com",
|
||||||
Status: domain.DiscoveryStatusUnmanaged,
|
Status: domain.DiscoveryStatusUnmanaged,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
discoveryRepo.Discovered[cert.ID] = cert
|
discoveryRepo.Discovered[cert.ID] = cert
|
||||||
|
|
||||||
@@ -456,11 +456,11 @@ func TestDismissDiscovered_Success(t *testing.T) {
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
cert := &domain.DiscoveredCertificate{
|
cert := &domain.DiscoveredCertificate{
|
||||||
ID: "dcert-1",
|
ID: "dcert-1",
|
||||||
CommonName: "example.com",
|
CommonName: "example.com",
|
||||||
Status: domain.DiscoveryStatusUnmanaged,
|
Status: domain.DiscoveryStatusUnmanaged,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
discoveryRepo.Discovered[cert.ID] = cert
|
discoveryRepo.Discovered[cert.ID] = cert
|
||||||
|
|
||||||
|
|||||||
@@ -71,10 +71,10 @@ func TestExportPEM_Success(t *testing.T) {
|
|||||||
Status: domain.CertificateStatusActive,
|
Status: domain.CertificateStatusActive,
|
||||||
},
|
},
|
||||||
&domain.CertificateVersion{
|
&domain.CertificateVersion{
|
||||||
ID: "cv-1",
|
ID: "cv-1",
|
||||||
CertificateID: "mc-test-1",
|
CertificateID: "mc-test-1",
|
||||||
SerialNumber: "abc123",
|
SerialNumber: "abc123",
|
||||||
PEMChain: fullPEM,
|
PEMChain: fullPEM,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
auditSvc := &AuditService{auditRepo: &mockAuditRepo{}}
|
auditSvc := &AuditService{auditRepo: &mockAuditRepo{}}
|
||||||
|
|||||||
@@ -14,19 +14,19 @@ import (
|
|||||||
|
|
||||||
// mockHealthCheckRepo implements the HealthCheckRepository interface for testing.
|
// mockHealthCheckRepo implements the HealthCheckRepository interface for testing.
|
||||||
type mockHealthCheckRepo struct {
|
type mockHealthCheckRepo struct {
|
||||||
checks map[string]*domain.EndpointHealthCheck
|
checks map[string]*domain.EndpointHealthCheck
|
||||||
history []*domain.HealthHistoryEntry
|
history []*domain.HealthHistoryEntry
|
||||||
createErr error
|
createErr error
|
||||||
getErr error
|
getErr error
|
||||||
updateErr error
|
updateErr error
|
||||||
deleteErr error
|
deleteErr error
|
||||||
listErr error
|
listErr error
|
||||||
listDueErr error
|
listDueErr error
|
||||||
getHistoryErr error
|
getHistoryErr error
|
||||||
recordHistoryErr error
|
recordHistoryErr error
|
||||||
purgeHistoryErr error
|
purgeHistoryErr error
|
||||||
getSummaryErr error
|
getSummaryErr error
|
||||||
getSummaryResult *domain.HealthCheckSummary
|
getSummaryResult *domain.HealthCheckSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockHealthCheckRepo() *mockHealthCheckRepo {
|
func newMockHealthCheckRepo() *mockHealthCheckRepo {
|
||||||
@@ -151,9 +151,9 @@ func TestHealthCheckService_Create_Success(t *testing.T) {
|
|||||||
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
|
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
|
||||||
|
|
||||||
check := &domain.EndpointHealthCheck{
|
check := &domain.EndpointHealthCheck{
|
||||||
Endpoint: "example.com:443",
|
Endpoint: "example.com:443",
|
||||||
Status: domain.HealthStatusUnknown,
|
Status: domain.HealthStatusUnknown,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
CheckIntervalSecs: 300,
|
CheckIntervalSecs: 300,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/shankar0123/certctl/internal/domain"
|
"github.com/shankar0123/certctl/internal/domain"
|
||||||
"github.com/shankar0123/certctl/internal/repository"
|
"github.com/shankar0123/certctl/internal/repository"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user