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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cmd/ate-setup/internal/steps/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,9 @@ func (e *Env) createJWTPool(ctx context.Context, namespace, name string) error {
if err != nil {
return fmt.Errorf("while generating the JWT authority for %s/%s: %w", namespace, name, err)
}
poolBytes, err := localjwtauthority.Marshal(&localjwtauthority.Pool{
Authorities: []*localjwtauthority.Authority{authority},
poolBytes, err := localjwtauthority.Marshal(&localjwtauthority.ConcretePool{
Authorities: []*localjwtauthority.Authority{authority},
ActiveForSigning: poolKeyID,
})
if err != nil {
return fmt.Errorf("while marshaling the JWT pool for %s/%s: %w", namespace, name, err)
Expand Down
34 changes: 11 additions & 23 deletions cmd/ateapi/internal/actoridentity/actoridentity.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ import (
"fmt"
"log/slog"
"net/url"
"os"
"path"
"time"

"github.com/agent-substrate/substrate/cmd/ateapi/internal/actoridjwt"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/controlapi"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache"
"github.com/agent-substrate/substrate/internal/actoridjwt"
"github.com/agent-substrate/substrate/internal/localca"
"github.com/agent-substrate/substrate/internal/localjwtauthority"
"github.com/agent-substrate/substrate/internal/principal"
Expand All @@ -49,11 +48,12 @@ import (
type Server struct {
ateapipb.UnimplementedActorIdentityServer

// TODO(identity): Issuer is probably logically a property of the JWT
// signing pool.
actorIdentityJWTIssuer string

// TODO: Cache the signing keys in memory, so we don't read from a file every time.
actorIDJWTPoolFile string
actorIDCAPool localca.Pool
actorIDJWTPool localjwtauthority.Pool
actorIDCAPool localca.Pool

// store is the actor database. MintCert consults it to confirm the caller
// is entitled to the actor it is asking for a credential for.
Expand All @@ -63,10 +63,10 @@ type Server struct {

var _ ateapipb.ActorIdentityServer = (*Server)(nil)

func New(actorIdentityJWTIssuer, actorIDJWTPoolFile string, actorIDCAPool localca.Pool, store store.Interface, workers *workercache.Cache) *Server {
func New(actorIdentityJWTIssuer string, actorIDJWTPool localjwtauthority.Pool, actorIDCAPool localca.Pool, store store.Interface, workers *workercache.Cache) *Server {
return &Server{
actorIdentityJWTIssuer: actorIdentityJWTIssuer,
actorIDJWTPoolFile: actorIDJWTPoolFile,
actorIDJWTPool: actorIDJWTPool,
actorIDCAPool: actorIDCAPool,
store: store,
workers: workers,
Expand Down Expand Up @@ -102,15 +102,9 @@ func (s *Server) MintJWT(ctx context.Context, req *ateapipb.MintJWTRequest) (*at

// TODO: Cross-check the verified caller and requested actor against the actor database.

// TODO: Cache signing keys in memory, so we don't read from disk every time.
signingPoolBytes, err := os.ReadFile(s.actorIDJWTPoolFile)
if err != nil {
return nil, fmt.Errorf("while reading signing pool bytes: %w", err)
}

signingPool, err := localjwtauthority.Unmarshal(signingPoolBytes)
if err != nil {
return nil, fmt.Errorf("while unmarshaling signing pool: %w", err)
// We only issue tokens with audience bindings.
if len(req.GetAudience()) == 0 {
return nil, fmt.Errorf("at least one audience must be requested")
}

actorClaims := &actoridjwt.Claims{
Expand All @@ -131,13 +125,7 @@ func (s *Server) MintJWT(ctx context.Context, req *ateapipb.MintJWTRequest) (*at
},
}

actorWireClaims, err := actoridjwt.ClaimsToWire(actorClaims)
if err != nil {
return nil, fmt.Errorf("while making actor JWT claims: %w", err)
}

// Assume the first authority is the one to use for signing.
actorJWT, err := actoridjwt.Sign(actorWireClaims, signingPool.Authorities[0].SigningKey, signingPool.Authorities[0].Algorithm, signingPool.Authorities[0].ID)
actorJWT, err := s.actorIDJWTPool.SignJWT(actorClaims)
if err != nil {
return nil, fmt.Errorf("while signing actor JWT: %w", err)
}
Expand Down
54 changes: 43 additions & 11 deletions cmd/ateapi/internal/actoridentity/actoridentity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache"
"github.com/agent-substrate/substrate/internal/localca"
"github.com/agent-substrate/substrate/internal/localjwtauthority"
"github.com/agent-substrate/substrate/internal/principal"
"github.com/agent-substrate/substrate/internal/resources"
"github.com/agent-substrate/substrate/internal/substratex509"
Expand Down Expand Up @@ -147,15 +148,24 @@ func ctxWithCert(cert *x509.Certificate) context.Context {
func newTestServer(t *testing.T, st store.Interface) *Server {
t.Helper()

ca, err := localca.GenerateCA("test-actor-ca", localca.KeyTypeED25519, 24*time.Hour)
certificateAuthority, err := localca.GenerateCA("test-actor-ca", localca.KeyTypeED25519, 24*time.Hour)
if err != nil {
t.Fatalf("generate CA: %v", err)
}
pool := &localca.ConcretePool{
CAs: []*localca.CA{ca},
certificateAuthorityPool := &localca.ConcretePool{
CAs: []*localca.CA{certificateAuthority},
ActiveForSigning: "test-actor-ca",
}

jwtAuthority, err := localjwtauthority.GenerateECDSAP256Authority("1")
if err != nil {
t.Fatalf("while generating JWT authority: %v", err)
}
jwtAuthorityPool := &localjwtauthority.ConcretePool{
Authorities: []*localjwtauthority.Authority{jwtAuthority},
ActiveForSigning: "1",
}

var workers *workercache.Cache
if st != nil {
workers = workercache.New(st, time.Hour)
Expand All @@ -165,7 +175,7 @@ func newTestServer(t *testing.T, st store.Interface) *Server {
t.Fatalf("start worker cache: %v", err)
}
}
return New("issuer", "", pool, st, workers)
return New("issuer", jwtAuthorityPool, certificateAuthorityPool, st, workers)
}

// staleWatchStore wraps a store with a WatchWorkers that never delivers,
Expand Down Expand Up @@ -319,12 +329,25 @@ func TestMintCertReadsThroughWorkerCacheMiss(t *testing.T) {
func newTestServerWithCache(t *testing.T, st store.Interface, workers *workercache.Cache) *Server {
t.Helper()

ca, err := localca.GenerateCA("test-actor-ca", localca.KeyTypeED25519, 24*time.Hour)
certificateAuthority, err := localca.GenerateCA("test-actor-ca", localca.KeyTypeED25519, 24*time.Hour)
if err != nil {
t.Fatalf("generate CA: %v", err)
}
pool := &localca.ConcretePool{CAs: []*localca.CA{ca}}
return New("issuer", "", pool, st, workers)
certificateAuthorityPool := &localca.ConcretePool{
CAs: []*localca.CA{certificateAuthority},
ActiveForSigning: "test-actor-ca",
}

jwtAuthority, err := localjwtauthority.GenerateECDSAP256Authority("1")
if err != nil {
t.Fatalf("while generating JWT authority: %v", err)
}
jwtAuthorityPool := &localjwtauthority.ConcretePool{
Authorities: []*localjwtauthority.Authority{jwtAuthority},
ActiveForSigning: "1",
}

return New("issuer", jwtAuthorityPool, certificateAuthorityPool, st, workers)
}

func TestMintJWTRequiresConfiguredJWTProvider(t *testing.T) {
Expand Down Expand Up @@ -909,16 +932,25 @@ func TestMintCertAuthorizesBeforeSigning(t *testing.T) {
t.Fatal(err)
}

ca, err := localca.GenerateCA("test-actor-ca", localca.KeyTypeED25519, 24*time.Hour)
certificateAuthority, err := localca.GenerateCA("test-actor-ca", localca.KeyTypeED25519, 24*time.Hour)
if err != nil {
t.Fatalf("generate CA: %v", err)
}
pool := &localca.ConcretePool{
CAs: []*localca.CA{ca},
certificateAuthorityPool := &localca.ConcretePool{
CAs: []*localca.CA{certificateAuthority},
ActiveForSigning: "test-actor-ca",
}

srv := New("issuer", "", pool, st, workers)
jwtAuthority, err := localjwtauthority.GenerateECDSAP256Authority("1")
if err != nil {
t.Fatalf("while generating JWT authority: %v", err)
}
jwtAuthorityPool := &localjwtauthority.ConcretePool{
Authorities: []*localjwtauthority.Authority{jwtAuthority},
ActiveForSigning: "1",
}

srv := New("issuer", jwtAuthorityPool, certificateAuthorityPool, st, workers)

actor, err := st.GetActor(ctx, resources.ActorRef{Atespace: testAtespace, Name: testActorName})
if err != nil {
Expand Down
170 changes: 0 additions & 170 deletions cmd/ateapi/internal/actoridjwt/actoridjwt.go

This file was deleted.

10 changes: 8 additions & 2 deletions cmd/ateapi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/agent-substrate/substrate/internal/ateinterceptors"
"github.com/agent-substrate/substrate/internal/credbundle"
"github.com/agent-substrate/substrate/internal/localca"
"github.com/agent-substrate/substrate/internal/localjwtauthority"
"github.com/agent-substrate/substrate/internal/serverboot"
"github.com/agent-substrate/substrate/internal/version"
"github.com/agent-substrate/substrate/internal/volume"
Expand Down Expand Up @@ -193,10 +194,15 @@ func main() {

actorIDCAPool, err := localca.NewRefreshingPool(*actorIDCAPoolFile)
if err != nil {
serverboot.Fatal(ctx, "while loading the Actor ID CA", err)
serverboot.Fatal(ctx, "while loading the Actor ID certificate authority pool: %w", err)
}

actorIdentitySrv := actoridentity.New(actorIdentityJWTIssuer, *actorIDJWTPoolFile, actorIDCAPool, persistence, workerCache)
actorIDJWTAuthorityPool, err := localjwtauthority.NewRefreshingPool(*actorIDJWTPoolFile)
if err != nil {
serverboot.Fatal(ctx, "while loading the Actor ID JWT authority pool: %w", err)
}

actorIdentitySrv := actoridentity.New(actorIdentityJWTIssuer, actorIDJWTAuthorityPool, actorIDCAPool, persistence, workerCache)

lisCfg := &net.ListenConfig{}
lis, err := lisCfg.Listen(ctx, "tcp", *listenAddr)
Expand Down
Loading
Loading