Skip to content

Commit

Permalink
POC to add taint upstream authority
Browse files Browse the repository at this point in the history
Signed-off-by: Marcos Yacob <marcosyacob@gmail.com>
  • Loading branch information
MarcosDY committed Jul 30, 2024
1 parent 14a3908 commit 7e5e679
Show file tree
Hide file tree
Showing 23 changed files with 274 additions and 252 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/spiffe/spire

go 1.22.3

replace github.com/spiffe/spire-api-sdk => github.com/MarcosDY/spire-api-sdk v1.0.0-pre.0.20240719151356-bb46a791f8fe

require (
cloud.google.com/go/iam v1.1.10
cloud.google.com/go/kms v1.18.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dX
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbOrtjYTXPtWNSyOFlcxkBU=
github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE=
github.com/MarcosDY/spire-api-sdk v1.0.0-pre.0.20240719151356-bb46a791f8fe h1:a6JXo33Rr/lsrRxU/jNkWS1vZgfQHb4YWCfvgCamI0E=
github.com/MarcosDY/spire-api-sdk v1.0.0-pre.0.20240719151356-bb46a791f8fe/go.mod h1:4uuhFlN6KBWjACRP3xXwrOTNnvaLp1zJs8Lribtr4fI=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
Expand Down Expand Up @@ -1420,8 +1422,6 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV
github.com/spiffe/go-spiffe/v2 v2.1.6/go.mod h1:eVDqm9xFvyqao6C+eQensb9ZPkyNEeaUbqbBpOhBnNk=
github.com/spiffe/go-spiffe/v2 v2.3.0 h1:g2jYNb/PDMB8I7mBGL2Zuq/Ur6hUhoroxGQFyD6tTj8=
github.com/spiffe/go-spiffe/v2 v2.3.0/go.mod h1:Oxsaio7DBgSNqhAO9i/9tLClaVlfRok7zvJnTV8ZyIY=
github.com/spiffe/spire-api-sdk v1.2.5-0.20240627195926-b5ac064f580b h1:k7ei1fQyt6+FbqDEAd90xaXLg52YuXueM+BRcoHZvEU=
github.com/spiffe/spire-api-sdk v1.2.5-0.20240627195926-b5ac064f580b/go.mod h1:4uuhFlN6KBWjACRP3xXwrOTNnvaLp1zJs8Lribtr4fI=
github.com/spiffe/spire-plugin-sdk v1.4.4-0.20230721151831-bf67dde4721d h1:LCRQGU6vOqKLfRrG+GJQrwMwDILcAddAEIf4/1PaSVc=
github.com/spiffe/spire-plugin-sdk v1.4.4-0.20230721151831-bf67dde4721d/go.mod h1:GA6o2PVLwyJdevT6KKt5ZXCY/ziAPna13y/seGk49Ik=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
3 changes: 2 additions & 1 deletion pkg/common/coretypes/jwtkey/apitypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func ToAPIProto(jwtKey JWTKey) (*apitypes.JWTKey, error) {
KeyId: id,
PublicKey: publicKey,
ExpiresAt: expiresAt,
Tainted: jwtKey.Tainted,
}, nil
}

Expand All @@ -23,7 +24,7 @@ func ToAPIFromPluginProto(pb *plugintypes.JWTKey) (*apitypes.JWTKey, error) {
return nil, nil
}

jwtKey, err := fromProtoFields(pb.KeyId, pb.PublicKey, pb.ExpiresAt)
jwtKey, err := fromProtoFields(pb.KeyId, pb.PublicKey, pb.ExpiresAt, pb.Tainted)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/common/coretypes/jwtkey/commontypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

func FromCommonProto(pb *common.PublicKey) (JWTKey, error) {
return fromProtoFields(pb.Kid, pb.PkixBytes, pb.NotAfter)
return fromProtoFields(pb.Kid, pb.PkixBytes, pb.NotAfter, pb.TaintedKey)
}

func FromCommonProtos(pbs []*common.PublicKey) ([]JWTKey, error) {
Expand Down
4 changes: 3 additions & 1 deletion pkg/common/coretypes/jwtkey/jwtkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type JWTKey struct {
ID string
PublicKey crypto.PublicKey
ExpiresAt time.Time
Tainted bool
}

func toProtoFields(jwtKey JWTKey) (string, []byte, int64, error) {
Expand All @@ -35,7 +36,7 @@ func toProtoFields(jwtKey JWTKey) (string, []byte, int64, error) {
return jwtKey.ID, publicKey, expiresAt, nil
}

func fromProtoFields(keyID string, publicKeyPKIX []byte, expiresAtUnix int64) (JWTKey, error) {
func fromProtoFields(keyID string, publicKeyPKIX []byte, expiresAtUnix int64, tainted bool) (JWTKey, error) {
if keyID == "" {
return JWTKey{}, errors.New("missing key ID for JWT key")
}
Expand All @@ -57,5 +58,6 @@ func fromProtoFields(keyID string, publicKeyPKIX []byte, expiresAtUnix int64) (J
ID: keyID,
PublicKey: publicKey,
ExpiresAt: expiresAt,
Tainted: tainted,
}, nil
}
4 changes: 2 additions & 2 deletions pkg/common/coretypes/jwtkey/plugintypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func FromPluginProto(pb *plugintypes.JWTKey) (JWTKey, error) {
return fromProtoFields(pb.KeyId, pb.PublicKey, pb.ExpiresAt)
return fromProtoFields(pb.KeyId, pb.PublicKey, pb.ExpiresAt, pb.Tainted)
}

func FromPluginProtos(pbs []*plugintypes.JWTKey) ([]JWTKey, error) {
Expand Down Expand Up @@ -80,7 +80,7 @@ func ToPluginFromAPIProto(pb *apitypes.JWTKey) (*plugintypes.JWTKey, error) {
return nil, nil
}

jwtKey, err := fromProtoFields(pb.KeyId, pb.PublicKey, pb.ExpiresAt)
jwtKey, err := fromProtoFields(pb.KeyId, pb.PublicKey, pb.ExpiresAt, pb.Tainted)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/common/coretypes/x509certificate/plugintypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ func ToPluginFromAPIProto(pb *apitypes.X509Certificate) (*plugintypes.X509Certif
return nil, err
}
return &plugintypes.X509Certificate{
Asn1: asn1,
Asn1: asn1,
Tainted: pb.Tainted,
}, nil
}

Expand Down
38 changes: 38 additions & 0 deletions pkg/common/coretypes/x509certificate/x509authority.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package x509certificate

import (
"crypto/x509"

plugintypes "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/types"
)

// TODO: may we call it Authority?
type X509Authority struct {
Certificate *x509.Certificate
Tainted bool
}

func ToX509AuthorityFromPluginProtos(pbs []*plugintypes.X509Certificate) ([]*X509Authority, error) {
var authorities []*X509Authority
for _, pb := range pbs {
authority, err := ToX509AuthorityFromPluginProto(pb)
if err != nil {
return nil, err
}
authorities = append(authorities, authority)
}

return authorities, nil
}

func ToX509AuthorityFromPluginProto(pb *plugintypes.X509Certificate) (*X509Authority, error) {
cert, err := fromProtoFields(pb.Asn1)
if err != nil {
return nil, err
}

return &X509Authority{
Certificate: cert,
Tainted: pb.Tainted,
}, nil
}
3 changes: 3 additions & 0 deletions pkg/common/telemetry/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@ const (
// with other tags to add clarity
Subject = "subject"

// Subject tags a certificate subject key ID
SubjectKeyId = "subject_key_id"

// SVIDMapSize is the gauge key for the size of the LRU cache SVID map
SVIDMapSize = "lru_cache_svid_map_size"

Expand Down
4 changes: 2 additions & 2 deletions pkg/common/telemetry/server/datastore/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,10 @@ func (w metricsWrapper) SetBundle(ctx context.Context, bundle *common.Bundle) (_
return w.ds.SetBundle(ctx, bundle)
}

func (w metricsWrapper) TaintX509CA(ctx context.Context, trustDomainID string, publicKeyToTaint crypto.PublicKey) (err error) {
func (w metricsWrapper) TaintX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToTaint string) (err error) {
callCounter := StartTaintX509CAByKeyCall(w.m)
defer callCounter.Done(&err)
return w.ds.TaintX509CA(ctx, trustDomainID, publicKeyToTaint)
return w.ds.TaintX509CA(ctx, trustDomainID, subjectKeyIDToTaint)
}

func (w metricsWrapper) RevokeX509CA(ctx context.Context, trustDomainID string, publicKeyToRevoke crypto.PublicKey) (err error) {
Expand Down
3 changes: 2 additions & 1 deletion pkg/server/api/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ func CertificatesToProto(rootCas []*common.Certificate) []*types.X509Certificate
var x509Authorities []*types.X509Certificate
for _, rootCA := range rootCas {
x509Authorities = append(x509Authorities, &types.X509Certificate{
Asn1: rootCA.DerBytes,
Asn1: rootCA.DerBytes,
Tainted: rootCA.TaintedKey,
})
}

Expand Down
45 changes: 44 additions & 1 deletion pkg/server/api/localauthority/v1/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/x509"
"errors"
"fmt"
"strings"

"github.com/sirupsen/logrus"
"github.com/spiffe/go-spiffe/v2/spiffeid"
Expand Down Expand Up @@ -33,6 +34,8 @@ type CAManager interface {
GetNextX509CASlot() manager.Slot
PrepareX509CA(ctx context.Context) error
RotateX509CA(ctx context.Context)

IsUpstreamAuthority() bool
}

// RegisterService registers the service on the gRPC server.
Expand Down Expand Up @@ -331,10 +334,15 @@ func (s *Service) ActivateX509Authority(ctx context.Context, req *localauthority
func (s *Service) TaintX509Authority(ctx context.Context, req *localauthorityv1.TaintX509AuthorityRequest) (*localauthorityv1.TaintX509AuthorityResponse, error) {
rpccontext.AddRPCAuditFields(ctx, buildAuditLogFields(req.AuthorityId))
log := rpccontext.Logger(ctx)

if req.AuthorityId != "" {
log = log.WithField(telemetry.LocalAuthorityID, req.AuthorityId)
}

if s.ca.IsUpstreamAuthority() {
return nil, api.MakeErr(log, codes.FailedPrecondition, "local authority can't be tainted if there is an upstream authorit", nil)
}

nextSlot := s.ca.GetNextX509CASlot()

switch {
Expand All @@ -355,7 +363,7 @@ func (s *Service) TaintX509Authority(ctx context.Context, req *localauthorityv1.
return nil, api.MakeErr(log, codes.InvalidArgument, "only Old local authorities can be tainted", fmt.Errorf("unsupported local authority status: %v", nextSlot.Status()))
}

if err := s.ds.TaintX509CA(ctx, s.td.IDString(), nextSlot.PublicKey()); err != nil {
if err := s.ds.TaintX509CA(ctx, s.td.IDString(), nextSlot.AuthorityID()); err != nil {
return nil, api.MakeErr(log, codes.Internal, "failed to taint X.509 authority", err)
}

Expand All @@ -371,6 +379,41 @@ func (s *Service) TaintX509Authority(ctx context.Context, req *localauthorityv1.
}, nil
}

func (s *Service) TaintX509UpstreamAuthority(ctx context.Context, req *localauthorityv1.TaintX509UpstreamAuthorityRequest) (*localauthorityv1.TaintX509UpstreamAuthorityResponse, error) {
rpccontext.AddRPCAuditFields(ctx, buildAuditLogFields(req.SubjectKeyId))
log := rpccontext.Logger(ctx)

if !s.ca.IsUpstreamAuthority() {
return nil, api.MakeErr(log, codes.FailedPrecondition, "upstream authority is not configured", nil)
}

if req.SubjectKeyId == "" {
return nil, api.MakeErr(log, codes.InvalidArgument, "subject key ID is required", nil)
}

// TODO: add a new field for SubjectKeyId
log = log.WithField(telemetry.SubjectKeyId, req.SubjectKeyId)

// Normalize SKID
subjectKeyIDToTaint := strings.ToLower(req.SubjectKeyId)

// TODO: may we validate that next slot contains an old authority and
// it is using the upstream authority to taint?
currentSlot := s.ca.GetCurrentX509CASlot()
currentSlotAuthorityKID := x509util.SubjectKeyIDToString(currentSlot.SigningAuthorityID())

if currentSlotAuthorityKID == subjectKeyIDToTaint {
return nil, api.MakeErr(log, codes.Internal, "unable to taint an active upstream authority", nil)
}

if err := s.ds.TaintX509CA(ctx, s.td.IDString(), subjectKeyIDToTaint); err != nil {
return nil, api.MakeErr(log, codes.Internal, "failed to taint upstream authority", err)

}

return &localauthorityv1.TaintX509UpstreamAuthorityResponse{}, nil
}

func (s *Service) RevokeX509Authority(ctx context.Context, req *localauthorityv1.RevokeX509AuthorityRequest) (*localauthorityv1.RevokeX509AuthorityResponse, error) {
rpccontext.AddRPCAuditFields(ctx, buildAuditLogFields(req.AuthorityId))
log := rpccontext.Logger(ctx)
Expand Down
10 changes: 8 additions & 2 deletions pkg/server/ca/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/andres-erbsen/clock"
"github.com/sirupsen/logrus"
"github.com/spiffe/go-spiffe/v2/spiffeid"
"github.com/spiffe/spire/pkg/common/coretypes/x509certificate"
"github.com/spiffe/spire/pkg/common/telemetry"
telemetry_server "github.com/spiffe/spire/pkg/common/telemetry/server"
"github.com/spiffe/spire/pkg/common/x509util"
Expand Down Expand Up @@ -244,6 +245,10 @@ func (m *Manager) PrepareX509CA(ctx context.Context) (err error) {
return nil
}

func (m *Manager) IsUpstreamAuthority() bool {
return m.upstreamClient != nil
}

func (m *Manager) ActivateX509CA(ctx context.Context) {
m.x509CAMutex.RLock()
defer m.x509CAMutex.RUnlock()
Expand Down Expand Up @@ -725,15 +730,16 @@ type bundleUpdater struct {
updated func()
}

func (u *bundleUpdater) AppendX509Roots(ctx context.Context, roots []*x509.Certificate) error {
func (u *bundleUpdater) AppendX509Roots(ctx context.Context, roots []*x509certificate.X509Authority) error {
bundle := &common.Bundle{
TrustDomainId: u.trustDomainID,
RootCas: make([]*common.Certificate, 0, len(roots)),
}

for _, root := range roots {
bundle.RootCas = append(bundle.RootCas, &common.Certificate{
DerBytes: root.Raw,
DerBytes: root.Certificate.Raw,
TaintedKey: root.Tainted,
})
}
if _, err := u.appendBundle(ctx, bundle); err != nil {
Expand Down
9 changes: 9 additions & 0 deletions pkg/server/ca/manager/slot.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Slot interface {
ShouldPrepareNext(now time.Time) bool
ShouldActivateNext(now time.Time) bool
Status() journal.Status
SigningAuthorityID() []byte
AuthorityID() string
PublicKey() crypto.PublicKey
NotAfter() time.Time
Expand Down Expand Up @@ -539,6 +540,10 @@ func newX509CASlot(id string) *x509CASlot {
}
}

func (s *x509CASlot) SigningAuthorityID() []byte {
return s.x509CA.Certificate.AuthorityKeyId
}

func (s *x509CASlot) KmKeyID() string {
return x509CAKmKeyID(s.id)
}
Expand Down Expand Up @@ -603,6 +608,10 @@ func (s *jwtKeySlot) AuthorityID() string {
return s.authorityID
}

func (s *jwtKeySlot) SigningAuthorityID() []byte {
return nil
}

func (s *jwtKeySlot) PublicKey() crypto.PublicKey {
if s.jwtKey == nil {
return nil
Expand Down
11 changes: 9 additions & 2 deletions pkg/server/ca/upstream_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sync"
"time"

"github.com/spiffe/spire/pkg/common/coretypes/x509certificate"
"github.com/spiffe/spire/pkg/server/plugin/upstreamauthority"
"github.com/spiffe/spire/proto/spire/common"
"google.golang.org/grpc/codes"
Expand All @@ -17,7 +18,7 @@ import (
// BundleUpdater is the interface used by the UpstreamClient to append bundle
// updates.
type BundleUpdater interface {
AppendX509Roots(ctx context.Context, roots []*x509.Certificate) error
AppendX509Roots(ctx context.Context, roots []*x509certificate.X509Authority) error
AppendJWTKeys(ctx context.Context, keys []*common.PublicKey) ([]*common.PublicKey, error)
LogError(err error, msg string)
}
Expand Down Expand Up @@ -139,10 +140,16 @@ func (u *UpstreamClient) runMintX509CAStream(ctx context.Context, csr []byte, tt
}
defer x509RootsStream.Close()

// Extract all roots certificates
var x509RootCerts []*x509.Certificate
for _, eachRoot := range x509Roots {
x509RootCerts = append(x509RootCerts, eachRoot.Certificate)
}

// Before we append the roots and return the response, we must first
// validate that the minted intermediate can sign a valid, conformant
// X509-SVID chain of trust using the provided callback.
if err := validateX509CA(x509CA, x509Roots); err != nil {
if err := validateX509CA(x509CA, x509RootCerts); err != nil {
err = status.Errorf(codes.InvalidArgument, "X509 CA minted by upstream authority is invalid: %v", err)
firstResultCh <- mintX509CAResult{err: err}
return
Expand Down
4 changes: 2 additions & 2 deletions pkg/server/cache/dscache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ func (ds *DatastoreCache) SetBundle(ctx context.Context, b *common.Bundle) (bund
return
}

func (ds *DatastoreCache) TaintX509CA(ctx context.Context, trustDomainID string, publicKeyToTaint crypto.PublicKey) (err error) {
if err = ds.DataStore.TaintX509CA(ctx, trustDomainID, publicKeyToTaint); err == nil {
func (ds *DatastoreCache) TaintX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToTaint string) (err error) {
if err = ds.DataStore.TaintX509CA(ctx, trustDomainID, subjectKeyIDToTaint); err == nil {
ds.invalidateBundleEntry(trustDomainID)
}
return
Expand Down
2 changes: 1 addition & 1 deletion pkg/server/datastore/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type DataStore interface {
UpdateBundle(context.Context, *common.Bundle, *common.BundleMask) (*common.Bundle, error)

// Keys
TaintX509CA(ctx context.Context, trustDomainID string, publicKeyToTaint crypto.PublicKey) error
TaintX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToTaint string) error
RevokeX509CA(ctx context.Context, trustDomainID string, publicKeyToRevoke crypto.PublicKey) error
TaintJWTKey(ctx context.Context, trustDomainID string, authorityID string) (*common.PublicKey, error)
RevokeJWTKey(ctx context.Context, trustDomainID string, authorityID string) (*common.PublicKey, error)
Expand Down
Loading

0 comments on commit 7e5e679

Please sign in to comment.