refactor: move acm domain and action adapters

This commit is contained in:
joshsiv-mulga
2026-10-02 09:54:16 +10:00
parent 13e99fe2b6
commit 2b945ad886
26 changed files with 93 additions and 101 deletions
+4 -4
View File
@@ -9,7 +9,7 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/admin"
handlers_acm "github.com/mulgadc/spinifex/spinifex/handlers/acm"
acmdomain "github.com/mulgadc/spinifex/spinifex/domains/acm"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/spf13/cobra"
)
@@ -86,9 +86,9 @@ func runCertForceRenew(cmd *cobra.Command, _ []string) {
defer cancel()
fmt.Printf("Force-renewing %s (account %s)...\n", certArn, accountID)
out, err := utils.NATSRequest[handlers_acm.ForceRenewCertificateOutput](
out, err := utils.NATSRequest[acmdomain.ForceRenewCertificateOutput](
ctx, nc, "acm.ForceRenewCertificate",
&handlers_acm.ForceRenewCertificateInput{CertificateArn: certArn},
&acmdomain.ForceRenewCertificateInput{CertificateArn: certArn},
30*time.Second, accountID,
)
if err != nil {
@@ -144,7 +144,7 @@ func runCertCreateTenantCA(cmd *cobra.Command, _ []string) {
fmt.Println("Tenant CA already exists — reporting current state (use --regenerate to replace it):")
}
ca, err := handlers_acm.LoadOrCreateTenantCA(certPath, keyPath, domains)
ca, err := acmdomain.LoadOrCreateTenantCA(certPath, keyPath, domains)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
+8 -8
View File
@@ -38,13 +38,13 @@ import (
"github.com/mulgadc/spinifex/spinifex/admin"
"github.com/mulgadc/spinifex/spinifex/bootstrap/config"
"github.com/mulgadc/spinifex/spinifex/bootstrap/preflight"
"github.com/mulgadc/spinifex/spinifex/foundation/state/clustersize"
acmdomain "github.com/mulgadc/spinifex/spinifex/domains/acm"
"github.com/mulgadc/spinifex/spinifex/domains/ec2/instancetypes"
handlers_ecr "github.com/mulgadc/spinifex/spinifex/domains/ecr"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/clustersize"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/telemetry"
handlers_acm "github.com/mulgadc/spinifex/spinifex/handlers/acm"
handlers_bedrock "github.com/mulgadc/spinifex/spinifex/handlers/bedrock"
handlers_dns "github.com/mulgadc/spinifex/spinifex/handlers/dns"
handlers_ec2_account "github.com/mulgadc/spinifex/spinifex/handlers/ec2/account"
@@ -168,8 +168,8 @@ type Daemon struct {
rdsReconciler *handlers_rds.Reconciler
bedrockService *handlers_bedrock.Service
bedrockReaper *handlers_bedrock.Reaper
acmService *handlers_acm.ACMServiceImpl
acmRenewalWorker *handlers_acm.Worker
acmService *acmdomain.ACMServiceImpl
acmRenewalWorker *acmdomain.Worker
ochreVectorService handlers_ochrevector.VectorService
ochreAppliance *handlers_ochrevector.Appliance
ochreBackupService *handlers_ochrevector.BackupService
@@ -2099,12 +2099,12 @@ func (d *Daemon) startCluster() error {
// backoff — the key file can legitimately not be written yet during a
// concurrent boot — but a master key that never arrives fails startCluster
// after the retry window instead of leaving acmService permanently nil.
d.acmService, err = initServiceWithRetry("ACM service", func() (*handlers_acm.ACMServiceImpl, error) {
d.acmService, err = initServiceWithRetry("ACM service", func() (*acmdomain.ACMServiceImpl, error) {
masterKey, mkErr := masterkey.ReadShared(filepath.Join(filepath.Dir(d.configPath), "master.key"))
if mkErr != nil {
return nil, fmt.Errorf("load ACM master key: %w", mkErr)
}
return handlers_acm.NewACMServiceImplWithNATS(d.ctx, d.config, d.natsConn, masterKey)
return acmdomain.NewACMServiceImplWithNATS(d.ctx, d.config, d.natsConn, masterKey)
})
if err != nil {
return fmt.Errorf("failed to initialize ACM service: %w", err)
@@ -2134,7 +2134,7 @@ func (d *Daemon) startCluster() error {
// operator can tell at a glance, from daemon startup logs alone, which one
// they are in.
configDir := filepath.Dir(d.configPath)
tenantCA, tenantCAErr := handlers_acm.LoadTenantCA(admin.TenantCACertPath(configDir), admin.TenantCAKeyPath(configDir))
tenantCA, tenantCAErr := acmdomain.LoadTenantCA(admin.TenantCACertPath(configDir), admin.TenantCAKeyPath(configDir))
if tenantCAErr != nil {
slog.Warn("ACM: tenant private CA not found; PRIVATE_CA certificate requests will fail until one is created",
"err", tenantCAErr)
@@ -2150,7 +2150,7 @@ func (d *Daemon) startCluster() error {
// tenant CA loaded later (or never, on a public-certificates-only
// deployment) is not a startup dependency — the worker just finds
// nothing to renew until one is wired.
d.acmRenewalWorker = handlers_acm.NewWorker(d.acmService, d.node)
d.acmRenewalWorker = acmdomain.NewWorker(d.acmService, d.node)
d.shutdownWg.Go(func() {
defer func() {
if r := recover(); r != nil {
@@ -1,4 +1,4 @@
package gateway_acm
package awsapi
import (
"errors"
@@ -1,10 +1,10 @@
package gateway_acm_test
package awsapi_test
import (
"testing"
awsapi "github.com/mulgadc/spinifex/spinifex/domains/acm/awsapi"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
gateway_acm "github.com/mulgadc/spinifex/spinifex/gateway/acm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -20,7 +20,7 @@ func certARN(id string) string {
func resolve(t *testing.T, action, body string) []string {
t.Helper()
resources, err := gateway_acm.ResourceARNs(action, testRegion, testAccountID, []byte(body))
resources, err := awsapi.ResourceARNs(action, testRegion, testAccountID, []byte(body))
require.NoError(t, err)
return resources
}
@@ -92,7 +92,7 @@ func TestResourceARNsUnparseableBody(t *testing.T) {
// A body carrying two spellings of one field is rejected: the gate and the
// handler would otherwise name different certificates.
func TestResourceARNsAmbiguousBody(t *testing.T) {
_, err := gateway_acm.ResourceARNs("DeleteCertificate", testRegion, testAccountID,
_, err := awsapi.ResourceARNs("DeleteCertificate", testRegion, testAccountID,
[]byte(`{"CertificateArn":"`+certARN("aaaa-1111")+`","certificateArn":"`+certARN("bbbb-2222")+`"}`))
require.Error(t, err)
assert.Equal(t, awserrors.ErrorInvalidParameterValue, err.Error())
@@ -111,7 +111,7 @@ func TestResourceARNsIdentifierIsAValue(t *testing.T) {
// An action absent from the dispatch table cannot reach the resolver, but if one
// ever did it fails closed rather than authorizing account-wide.
func TestResourceARNsUnknownAction(t *testing.T) {
_, err := gateway_acm.ResourceARNs("BogusAction", testRegion, testAccountID, []byte(`{}`))
_, err := awsapi.ResourceARNs("BogusAction", testRegion, testAccountID, []byte(`{}`))
require.Error(t, err)
assert.Equal(t, awserrors.ErrorInvalidAction, err.Error())
}
@@ -119,15 +119,15 @@ func TestResourceARNsUnknownAction(t *testing.T) {
// Without a region or an account there is no ARN to build, and every action
// falls back to the account-wide resource.
func TestResourceARNsWithoutAnAnchor(t *testing.T) {
for _, action := range gateway_acm.ScopedActions() {
resources, err := gateway_acm.ResourceARNs(action, "", "", []byte(`{"CertificateArn":"`+certARN("aaaa-1111")+`"}`))
for _, action := range awsapi.ScopedActions() {
resources, err := awsapi.ResourceARNs(action, "", "", []byte(`{"CertificateArn":"`+certARN("aaaa-1111")+`"}`))
require.NoError(t, err)
assert.Equal(t, []string{"*"}, resources, "action %q", action)
}
}
func TestHasScope(t *testing.T) {
assert.True(t, gateway_acm.HasScope("DeleteCertificate"))
assert.False(t, gateway_acm.HasScope("BogusAction"))
assert.Len(t, gateway_acm.ScopedActions(), 9)
assert.True(t, awsapi.HasScope("DeleteCertificate"))
assert.False(t, awsapi.HasScope("BogusAction"))
assert.Len(t, awsapi.ScopedActions(), 9)
}
@@ -1,4 +1,4 @@
package gateway_acm
package awsapi
import (
"context"
@@ -14,6 +14,15 @@ import (
// natsTimeout bounds the gateway's wait for a daemon-side ACM response.
const natsTimeout = 30 * time.Second
// RequestCertificate — CertificateManager.RequestCertificate.
func RequestCertificate(ctx context.Context, natsConn *nats.Conn, accountID string, body []byte) (*acm.RequestCertificateOutput, error) {
input := new(acm.RequestCertificateInput)
if err := unmarshalIfBody(body, input); err != nil {
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
}
return utils.NATSRequest[acm.RequestCertificateOutput](ctx, natsConn, "acm.RequestCertificate", input, natsTimeout, accountID)
}
// ImportCertificate — CertificateManager.ImportCertificate.
func ImportCertificate(ctx context.Context, natsConn *nats.Conn, accountID string, body []byte) (*acm.ImportCertificateOutput, error) {
input := new(acm.ImportCertificateInput)
@@ -1,12 +1,12 @@
package gateway_acm
package awsapi
import (
"context"
"net/http/httptest"
"testing"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -1,7 +1,7 @@
// Package gateway_acm is the HTTP-side glue between awsgw and the ACM handlers.
// ACM speaks AWS JSON 1.1 (X-Amz-Target dispatch); errors are handled centrally
// by the shared gateway ErrorHandler.
package gateway_acm
// Package awsapi adapts ACM's AWS JSON 1.1 actions to the ACM domain's
// NATS-backed service contract. Generic gateway routing and authorization stay
// at the ingress boundary.
package awsapi
import (
"encoding/json"
@@ -1,4 +1,4 @@
package handlers_acm
package acm
import (
"context"
@@ -1,4 +1,5 @@
package handlers_acm
// Package acm owns ACM certificate state, issuance, renewal and AWS action adapters.
package acm
import (
"crypto/rand"
@@ -1,4 +1,4 @@
package handlers_acm
package acm
import (
"crypto/rand"
@@ -1,4 +1,4 @@
package handlers_acm
package acm
// renewal.go implements the PRIVATE_CA renewal worker described in the ACM
// design doc's "Renewal" section: a scan-and-lease worker, sharing the
@@ -1,4 +1,4 @@
package handlers_acm
package acm
import (
"context"
@@ -1,4 +1,4 @@
package handlers_acm
package acm
import (
"context"
@@ -32,7 +32,7 @@ const (
// CertAuthority issues leaf certificates from the tenant private CA and
// authorizes domains against the CA's own x509 name constraints. Satisfied by
// *TenantCA (handlers/acm/privateca.go), which is implemented separately;
// *TenantCA (privateca.go), which is implemented separately;
// defined here as a narrow interface so this package builds and is testable
// with a fake independently of that file.
type CertAuthority interface {
@@ -1,4 +1,4 @@
package handlers_acm
package acm
import (
"context"
@@ -20,10 +20,10 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/private/protocol/json/jsonutil"
"github.com/aws/aws-sdk-go/service/acm"
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -1,4 +1,4 @@
package handlers_acm
package acm
import (
"context"
@@ -12,9 +12,9 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
)
@@ -1,4 +1,4 @@
package handlers_acm
package acm
import (
"encoding/json"
@@ -7,8 +7,8 @@ import (
"testing"
"time"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
"github.com/mulgadc/spinifex/internal/testkit"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -1,4 +1,4 @@
package handlers_acm
package acm
import (
"context"
@@ -1,4 +1,4 @@
package handlers_acm
package acm
import (
"testing"
+14 -32
View File
@@ -2,65 +2,47 @@ package gateway
import (
"context"
"encoding/json"
"errors"
"log/slog"
"net/http"
"strings"
"time"
"github.com/aws/aws-sdk-go/service/acm"
acmawsapi "github.com/mulgadc/spinifex/spinifex/domains/acm/awsapi"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
gateway_acm "github.com/mulgadc/spinifex/spinifex/gateway/acm"
"github.com/mulgadc/spinifex/spinifex/utils"
)
// acmNATSTimeout bounds the gateway's wait for a daemon-side ACM response.
// RequestCertificate itself returns immediately (issuance is asynchronous),
// so this is a network/queueing budget, not an issuance one.
const acmNATSTimeout = 30 * time.Second
// acmHandler invokes a per-action ACM gateway function.
// acmHandler invokes a per-action ACM domain adapter after generic gateway
// authentication and authorization have completed.
type acmHandler func(ctx context.Context, gw *GatewayConfig, accountID string, body []byte) (any, error)
// acmActions maps the action suffix of X-Amz-Target (CertificateManager.<Action>) to its handler.
var acmActions = map[string]acmHandler{
"ImportCertificate": func(ctx context.Context, gw *GatewayConfig, acct string, b []byte) (any, error) {
return gateway_acm.ImportCertificate(ctx, gw.NATSConn, acct, b)
return acmawsapi.ImportCertificate(ctx, gw.NATSConn, acct, b)
},
// RequestCertificate calls the daemon over NATS directly (rather than
// through package gateway_acm, which does not yet have a helper for it) —
// the same "acm.RequestCertificate" subject and request/response shape
// utils.NATSRequest uses everywhere else in this table.
"RequestCertificate": func(ctx context.Context, gw *GatewayConfig, acct string, b []byte) (any, error) {
input := new(acm.RequestCertificateInput)
if len(b) > 0 {
if err := json.Unmarshal(b, input); err != nil {
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
}
}
return utils.NATSRequest[acm.RequestCertificateOutput](ctx, gw.NATSConn, "acm.RequestCertificate", input, acmNATSTimeout, acct)
return acmawsapi.RequestCertificate(ctx, gw.NATSConn, acct, b)
},
"DescribeCertificate": func(ctx context.Context, gw *GatewayConfig, acct string, b []byte) (any, error) {
return gateway_acm.DescribeCertificate(ctx, gw.NATSConn, acct, b)
return acmawsapi.DescribeCertificate(ctx, gw.NATSConn, acct, b)
},
"GetCertificate": func(ctx context.Context, gw *GatewayConfig, acct string, b []byte) (any, error) {
return gateway_acm.GetCertificate(ctx, gw.NATSConn, acct, b)
return acmawsapi.GetCertificate(ctx, gw.NATSConn, acct, b)
},
"ListCertificates": func(ctx context.Context, gw *GatewayConfig, acct string, b []byte) (any, error) {
return gateway_acm.ListCertificates(ctx, gw.NATSConn, acct, b)
return acmawsapi.ListCertificates(ctx, gw.NATSConn, acct, b)
},
"DeleteCertificate": func(ctx context.Context, gw *GatewayConfig, acct string, b []byte) (any, error) {
return gateway_acm.DeleteCertificate(ctx, gw.NATSConn, acct, b)
return acmawsapi.DeleteCertificate(ctx, gw.NATSConn, acct, b)
},
"ListTagsForCertificate": func(ctx context.Context, gw *GatewayConfig, acct string, b []byte) (any, error) {
return gateway_acm.ListTagsForCertificate(ctx, gw.NATSConn, acct, b)
return acmawsapi.ListTagsForCertificate(ctx, gw.NATSConn, acct, b)
},
"AddTagsToCertificate": func(ctx context.Context, gw *GatewayConfig, acct string, b []byte) (any, error) {
return gateway_acm.AddTagsToCertificate(ctx, gw.NATSConn, acct, b)
return acmawsapi.AddTagsToCertificate(ctx, gw.NATSConn, acct, b)
},
"RemoveTagsFromCertificate": func(ctx context.Context, gw *GatewayConfig, acct string, b []byte) (any, error) {
return gateway_acm.RemoveTagsFromCertificate(ctx, gw.NATSConn, acct, b)
return acmawsapi.RemoveTagsFromCertificate(ctx, gw.NATSConn, acct, b)
},
}
@@ -103,7 +85,7 @@ func (gw *GatewayConfig) ACM_Request(w http.ResponseWriter, r *http.Request) err
return err
}
resources, err := gateway_acm.ResourceARNs(action, gw.Region, accountID, body)
resources, err := acmawsapi.ResourceARNs(action, gw.Region, accountID, body)
if err != nil {
return err
}
@@ -120,6 +102,6 @@ func (gw *GatewayConfig) ACM_Request(w http.ResponseWriter, r *http.Request) err
return err
}
gateway_acm.WriteJSONResponse(w, output)
acmawsapi.WriteJSONResponse(w, output)
return nil
}
@@ -6,7 +6,7 @@ package gateway
import (
"testing"
gateway_acm "github.com/mulgadc/spinifex/spinifex/gateway/acm"
acmawsapi "github.com/mulgadc/spinifex/spinifex/domains/acm/awsapi"
"github.com/stretchr/testify/assert"
)
@@ -15,13 +15,13 @@ import (
// behind by a deleted or renamed action fails too.
func TestACMScopeTableIsExhaustive(t *testing.T) {
for action := range acmActions {
assert.True(t, gateway_acm.HasScope(action),
"acm action %q has no resource scope entry: add one to acmScopes in gateway/acm/authz.go", action)
assert.True(t, acmawsapi.HasScope(action),
"acm action %q has no resource scope entry: add one to domains/acm/awsapi/authz.go", action)
}
for _, action := range gateway_acm.ScopedActions() {
for _, action := range acmawsapi.ScopedActions() {
_, ok := acmActions[action]
assert.True(t, ok,
"acmScopes has an entry for %q, which the dispatch table does not serve: remove it from gateway/acm/authz.go", action)
"acmScopes has an entry for %q, which the dispatch table does not serve: remove it from domains/acm/awsapi/authz.go", action)
}
}
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"testing"
"github.com/mulgadc/bluebottle/pkg/sigv4"
acmawsapi "github.com/mulgadc/spinifex/spinifex/domains/acm/awsapi"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
gateway_acm "github.com/mulgadc/spinifex/spinifex/gateway/acm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -125,7 +125,7 @@ func TestACMRequest_ListIsAccountLevel(t *testing.T) {
func TestACMRequest_WildcardPolicyStillPermitsEveryAction(t *testing.T) {
gw := scopedPolicyGateway(statement("Allow", "acm:*", "*"))
for _, action := range gateway_acm.ScopedActions() {
for _, action := range acmawsapi.ScopedActions() {
t.Run(action, func(t *testing.T) {
assertPermitted(t, dispatchACM(t, gw, action, `{"CertificateArn":"`+acmCertARN("aaaa-1111")+`"}`))
})
+4 -4
View File
@@ -15,9 +15,9 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/acm"
"github.com/aws/aws-sdk-go/service/elbv2"
handlers_acm "github.com/mulgadc/spinifex/spinifex/handlers/acm"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
"github.com/mulgadc/spinifex/internal/testkit"
acmdomain "github.com/mulgadc/spinifex/spinifex/domains/acm"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -343,7 +343,7 @@ func TestReconcileCertInUseIndex_RestoresRenewalFanOut(t *testing.T) {
elbv2Svc, err := NewELBv2ServiceImplWithNATS(nil, nc, masterKey)
require.NoError(t, err)
acmSvc, err := handlers_acm.NewACMServiceImplWithNATS(context.Background(), nil, nc, masterKey)
acmSvc, err := acmdomain.NewACMServiceImplWithNATS(context.Background(), nil, nc, masterKey)
require.NoError(t, err)
acmSvc.CertMaterialUpdated = elbv2Svc.UpdateStoredConfigForCert
@@ -443,7 +443,7 @@ func TestACMReimport_FansOutToInUseLoadBalancer(t *testing.T) {
elbv2Svc, err := NewELBv2ServiceImplWithNATS(nil, nc, masterKey)
require.NoError(t, err)
acmSvc, err := handlers_acm.NewACMServiceImplWithNATS(context.Background(), nil, nc, masterKey)
acmSvc, err := acmdomain.NewACMServiceImplWithNATS(context.Background(), nil, nc, masterKey)
require.NoError(t, err)
// Mirrors the daemon.go wiring between the two services.
acmSvc.CertMaterialUpdated = elbv2Svc.UpdateStoredConfigForCert
@@ -13,11 +13,11 @@ import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/mulgadc/spinifex/internal/testkit"
acmdomain "github.com/mulgadc/spinifex/spinifex/domains/acm"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
handlers_acm "github.com/mulgadc/spinifex/spinifex/handlers/acm"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
"github.com/mulgadc/spinifex/spinifex/lbagent"
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -105,7 +105,7 @@ func TestConfigCertHash_ChangesOnRotation(t *testing.T) {
func putTestCert(t *testing.T, svc *ELBv2ServiceImpl, arn, account, leaf, chain, key string) {
t.Helper()
require.NoError(t, svc.acmStore.PutCert(t.Context(), &handlers_acm.CertRecord{
require.NoError(t, svc.acmStore.PutCert(t.Context(), &acmdomain.CertRecord{
CertificateArn: arn,
AccountID: account,
Certificate: leaf,
@@ -155,7 +155,7 @@ func TestResolveCertPEM_CrossesACMServiceKeyBoundary(t *testing.T) {
// ACM-service-style writer: its own Store over the same bucket, wired with
// the same deployment key rather than svc's own acmStore.
acmStore, err := handlers_acm.NewStore(t.Context(), nc, masterKey)
acmStore, err := acmdomain.NewStore(t.Context(), nc, masterKey)
require.NoError(t, err)
// A realistic EC private key PEM, not a placeholder — a non-PEM value
@@ -168,7 +168,7 @@ func TestResolveCertPEM_CrossesACMServiceKeyBoundary(t *testing.T) {
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}))
arn := "arn:aws:acm:ap-southeast-2:123456789012:certificate/cross-boundary"
require.NoError(t, acmStore.PutCert(t.Context(), &handlers_acm.CertRecord{
require.NoError(t, acmStore.PutCert(t.Context(), &acmdomain.CertRecord{
CertificateArn: arn,
AccountID: testAccountID,
Certificate: "LEAF",
+3 -3
View File
@@ -20,11 +20,11 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/mulgadc/spinifex/spinifex/bootstrap/config"
acmdomain "github.com/mulgadc/spinifex/spinifex/domains/acm"
resourcearn "github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
handlers_acm "github.com/mulgadc/spinifex/spinifex/handlers/acm"
handlers_dns "github.com/mulgadc/spinifex/spinifex/handlers/dns"
handlers_ec2_vpc "github.com/mulgadc/spinifex/spinifex/handlers/ec2/vpc"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
@@ -103,7 +103,7 @@ var _ ELBv2Service = (*ELBv2ServiceImpl)(nil)
type ELBv2ServiceImpl struct {
config *config.Config
store *Store
acmStore *handlers_acm.Store // resolves listener cert ARNs → PEM; nil-safe (HTTPS unavailable when nil)
acmStore *acmdomain.Store // resolves listener cert ARNs → PEM; nil-safe (HTTPS unavailable when nil)
nc *nats.Conn // NATS connection for JetStream KV store
VPCService *handlers_ec2_vpc.VPCServiceImpl // nil-safe: ENI ops skipped when nil (e.g. in tests)
InstanceLauncher SystemInstanceLauncher // nil-safe: system VM ops skipped when nil
@@ -173,7 +173,7 @@ func NewELBv2ServiceImplWithNATS(cfg *config.Config, nc *nats.Conn, masterKey []
// found" everywhere it is consulted (resolveCertPEM, validateListenerCerts),
// so a construction failure here must fail the whole service rather than
// silently degrade every HTTPS listener.
acmStore, acmErr := handlers_acm.NewStore(ctx, nc, masterKey)
acmStore, acmErr := acmdomain.NewStore(ctx, nc, masterKey)
if acmErr != nil {
cancel()
return nil, fmt.Errorf("failed to create ELBv2 ACM store: %w", acmErr)
+2 -2
View File
@@ -18,9 +18,9 @@ import (
awscreds "github.com/aws/aws-sdk-go/aws/credentials"
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
"github.com/aws/aws-sdk-go/service/iam"
acmawsapi "github.com/mulgadc/spinifex/spinifex/domains/acm/awsapi"
awsapi "github.com/mulgadc/spinifex/spinifex/domains/ecr/awsapi"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
gateway_acm "github.com/mulgadc/spinifex/spinifex/gateway/acm"
gateway_ecs "github.com/mulgadc/spinifex/spinifex/gateway/ecs"
gateway_eks "github.com/mulgadc/spinifex/spinifex/gateway/eks"
gateway_elbv2 "github.com/mulgadc/spinifex/spinifex/gateway/elbv2"
@@ -329,7 +329,7 @@ func sameHelper(casedAction string) string {
func scopedServices() []scopedService {
return []scopedService{
{name: "acm", actions: gateway_acm.ScopedActions(), uncased: map[string]string{
{name: "acm", actions: acmawsapi.ScopedActions(), uncased: map[string]string{
"GetCertificate": sameHelper("DescribeCertificate"),
"ListTagsForCertificate": sameHelper("DescribeCertificate"),
"AddTagsToCertificate": sameHelper("DescribeCertificate"),
+3 -3
View File
@@ -7,7 +7,7 @@ import (
"testing"
"github.com/mulgadc/spinifex/spinifex/bootstrap/config"
handlers_acm "github.com/mulgadc/spinifex/spinifex/handlers/acm"
acmdomain "github.com/mulgadc/spinifex/spinifex/domains/acm"
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
handlers_eks "github.com/mulgadc/spinifex/spinifex/handlers/eks"
handlers_elbv2 "github.com/mulgadc/spinifex/spinifex/handlers/elbv2"
@@ -26,12 +26,12 @@ func StartServiceDaemonLite(t *testing.T, gw *Gateway) {
masterKey, err := handlers_iam.GenerateMasterKey()
require.NoError(t, err)
acm, err := handlers_acm.NewACMServiceImplWithNATS(t.Context(), cfg, nc, masterKey)
acm, err := acmdomain.NewACMServiceImplWithNATS(t.Context(), cfg, nc, masterKey)
require.NoError(t, err, "construct ACM service")
// Without a tenant CA no mode can issue, so every RequestCertificate would
// be refused. The generated requests name example.com domains.
caDir := t.TempDir()
acm.TenantCA, err = handlers_acm.LoadOrCreateTenantCA(filepath.Join(caDir, "ca.pem"), filepath.Join(caDir, "ca.key"), []string{"example.com"})
acm.TenantCA, err = acmdomain.LoadOrCreateTenantCA(filepath.Join(caDir, "ca.pem"), filepath.Join(caDir, "ca.key"), []string{"example.com"})
require.NoError(t, err, "create ACM tenant CA")
subscribeServiceMethods(t, nc, "acm", acm)