diff --git a/go.mod b/go.mod index 4f48cd0a62..1ae4284013 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,7 @@ require ( github.com/sigstore/sigstore v1.8.8 github.com/sirupsen/logrus v1.9.3 github.com/spiffe/go-spiffe/v2 v2.3.0 - github.com/spiffe/spire-api-sdk v1.2.5-0.20240722174251-0116a7186c35 + github.com/spiffe/spire-api-sdk v1.2.5-0.20240807182354-18e423ce2c1c github.com/spiffe/spire-plugin-sdk v1.4.4-0.20230721151831-bf67dde4721d github.com/stretchr/testify v1.9.0 github.com/uber-go/tally/v4 v4.1.16 diff --git a/go.sum b/go.sum index 6f745b357e..2c7d007626 100644 --- a/go.sum +++ b/go.sum @@ -1426,8 +1426,8 @@ github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+ 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.20240722174251-0116a7186c35 h1:Ah7jJvfjw2fYXtSJF69lWokspl5Vhge0yiSi/mFhzhM= -github.com/spiffe/spire-api-sdk v1.2.5-0.20240722174251-0116a7186c35/go.mod h1:4uuhFlN6KBWjACRP3xXwrOTNnvaLp1zJs8Lribtr4fI= +github.com/spiffe/spire-api-sdk v1.2.5-0.20240807182354-18e423ce2c1c h1:lK/B2paDUiqbngUGsLxDBmNX/BsG2yKxS8W/iGT+x2c= +github.com/spiffe/spire-api-sdk v1.2.5-0.20240807182354-18e423ce2c1c/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= diff --git a/pkg/common/coretypes/bundle/bundle_test.go b/pkg/common/coretypes/bundle/bundle_test.go index ef3d1744e8..d297393f5d 100644 --- a/pkg/common/coretypes/bundle/bundle_test.go +++ b/pkg/common/coretypes/bundle/bundle_test.go @@ -32,12 +32,15 @@ MWnIPs59/JF8AiBeKSM/rkL2igQchDTvlJJWsyk9YL8UZI/XfZO7907TWA== pkixBytes, _ = x509.MarshalPKIXPublicKey(publicKey) apiJWTAuthoritiesGood = []*apitypes.JWTKey{ {KeyId: "ID", PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix()}, + {KeyId: "IDTainted", PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix(), Tainted: true}, } apiJWTAuthoritiesBad = []*apitypes.JWTKey{ {PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix()}, + {PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix()}, } apiX509AuthoritiesGood = []*apitypes.X509Certificate{ {Asn1: root.Raw}, + {Asn1: root.Raw, Tainted: true}, } apiX509AuthoritiesBad = []*apitypes.X509Certificate{ {Asn1: []byte("malformed")}, @@ -71,9 +74,12 @@ MWnIPs59/JF8AiBeKSM/rkL2igQchDTvlJJWsyk9YL8UZI/XfZO7907TWA== SequenceNumber: 2, } commonInvalidTD = &common.Bundle{ - TrustDomainId: "not a trustdomain id", - RootCas: []*common.Certificate{{DerBytes: root.Raw}}, - JwtSigningKeys: []*common.PublicKey{{Kid: "ID", PkixBytes: pkixBytes, NotAfter: expiresAt.Unix()}}, + TrustDomainId: "not a trustdomain id", + RootCas: []*common.Certificate{{DerBytes: root.Raw}}, + JwtSigningKeys: []*common.PublicKey{ + {Kid: "ID", PkixBytes: pkixBytes, NotAfter: expiresAt.Unix()}, + {Kid: "IDTainted", PkixBytes: pkixBytes, NotAfter: expiresAt.Unix(), TaintedKey: true}, + }, RefreshHint: 1, SequenceNumber: 2, } @@ -93,12 +99,14 @@ MWnIPs59/JF8AiBeKSM/rkL2igQchDTvlJJWsyk9YL8UZI/XfZO7907TWA== } pluginJWTAuthoritiesGood = []*plugintypes.JWTKey{ {KeyId: "ID", PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix()}, + {KeyId: "IDTainted", PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix(), Tainted: true}, } pluginJWTAuthoritiesBad = []*plugintypes.JWTKey{ {PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix()}, } pluginX509AuthoritiesGood = []*plugintypes.X509Certificate{ {Asn1: root.Raw}, + {Asn1: root.Raw, Tainted: true}, } pluginX509AuthoritiesBad = []*plugintypes.X509Certificate{ {Asn1: []byte("malformed")}, @@ -132,9 +140,15 @@ MWnIPs59/JF8AiBeKSM/rkL2igQchDTvlJJWsyk9YL8UZI/XfZO7907TWA== SequenceNumber: 2, } commonGood = &common.Bundle{ - TrustDomainId: "spiffe://example.org", - RootCas: []*common.Certificate{{DerBytes: root.Raw}}, - JwtSigningKeys: []*common.PublicKey{{Kid: "ID", PkixBytes: pkixBytes, NotAfter: expiresAt.Unix()}}, + TrustDomainId: "spiffe://example.org", + RootCas: []*common.Certificate{ + {DerBytes: root.Raw}, + {DerBytes: root.Raw, TaintedKey: true}, + }, + JwtSigningKeys: []*common.PublicKey{ + {Kid: "ID", PkixBytes: pkixBytes, NotAfter: expiresAt.Unix()}, + {Kid: "IDTainted", PkixBytes: pkixBytes, NotAfter: expiresAt.Unix(), TaintedKey: true}, + }, RefreshHint: 1, SequenceNumber: 2, } diff --git a/pkg/common/coretypes/jwtkey/apitypes.go b/pkg/common/coretypes/jwtkey/apitypes.go index 49eaf42c3a..cca9f6e7bd 100644 --- a/pkg/common/coretypes/jwtkey/apitypes.go +++ b/pkg/common/coretypes/jwtkey/apitypes.go @@ -6,7 +6,7 @@ import ( ) func ToAPIProto(jwtKey JWTKey) (*apitypes.JWTKey, error) { - id, publicKey, expiresAt, err := toProtoFields(jwtKey) + id, publicKey, expiresAt, tainted, err := toProtoFields(jwtKey) if err != nil { return nil, err } @@ -15,6 +15,7 @@ func ToAPIProto(jwtKey JWTKey) (*apitypes.JWTKey, error) { KeyId: id, PublicKey: publicKey, ExpiresAt: expiresAt, + Tainted: tainted, }, nil } @@ -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 } diff --git a/pkg/common/coretypes/jwtkey/commontypes.go b/pkg/common/coretypes/jwtkey/commontypes.go index dc5ba1ea6f..3b8b0e883c 100644 --- a/pkg/common/coretypes/jwtkey/commontypes.go +++ b/pkg/common/coretypes/jwtkey/commontypes.go @@ -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) { @@ -25,14 +25,15 @@ func FromCommonProtos(pbs []*common.PublicKey) ([]JWTKey, error) { } func ToCommonProto(jwtKey JWTKey) (*common.PublicKey, error) { - id, publicKey, expiresAt, err := toProtoFields(jwtKey) + id, publicKey, expiresAt, tainted, err := toProtoFields(jwtKey) if err != nil { return nil, err } return &common.PublicKey{ - Kid: id, - PkixBytes: publicKey, - NotAfter: expiresAt, + Kid: id, + PkixBytes: publicKey, + NotAfter: expiresAt, + TaintedKey: tainted, }, nil } diff --git a/pkg/common/coretypes/jwtkey/jwtkey.go b/pkg/common/coretypes/jwtkey/jwtkey.go index f7a52006fb..fb960d0573 100644 --- a/pkg/common/coretypes/jwtkey/jwtkey.go +++ b/pkg/common/coretypes/jwtkey/jwtkey.go @@ -12,30 +12,30 @@ type JWTKey struct { ID string PublicKey crypto.PublicKey ExpiresAt time.Time + Tainted bool } -func toProtoFields(jwtKey JWTKey) (string, []byte, int64, error) { +func toProtoFields(jwtKey JWTKey) (id string, publicKey []byte, expiresAt int64, tainted bool, err error) { if jwtKey.ID == "" { - return "", nil, 0, errors.New("missing key ID for JWT key") + return "", nil, 0, false, errors.New("missing key ID for JWT key") } if jwtKey.PublicKey == nil { - return "", nil, 0, fmt.Errorf("missing public key for JWT key %q", jwtKey.ID) + return "", nil, 0, false, fmt.Errorf("missing public key for JWT key %q", jwtKey.ID) } - publicKey, err := x509.MarshalPKIXPublicKey(jwtKey.PublicKey) + publicKey, err = x509.MarshalPKIXPublicKey(jwtKey.PublicKey) if err != nil { - return "", nil, 0, fmt.Errorf("failed to marshal public key for JWT key %q: %w", jwtKey.ID, err) + return "", nil, 0, false, fmt.Errorf("failed to marshal public key for JWT key %q: %w", jwtKey.ID, err) } - var expiresAt int64 if !jwtKey.ExpiresAt.IsZero() { expiresAt = jwtKey.ExpiresAt.Unix() } - return jwtKey.ID, publicKey, expiresAt, nil + return jwtKey.ID, publicKey, expiresAt, jwtKey.Tainted, 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") } @@ -57,5 +57,6 @@ func fromProtoFields(keyID string, publicKeyPKIX []byte, expiresAtUnix int64) (J ID: keyID, PublicKey: publicKey, ExpiresAt: expiresAt, + Tainted: tainted, }, nil } diff --git a/pkg/common/coretypes/jwtkey/jwtkey_test.go b/pkg/common/coretypes/jwtkey/jwtkey_test.go index 5910a57898..6260a00e69 100644 --- a/pkg/common/coretypes/jwtkey/jwtkey_test.go +++ b/pkg/common/coretypes/jwtkey/jwtkey_test.go @@ -22,21 +22,25 @@ var ( pkixBytes, _ = x509.MarshalPKIXPublicKey(publicKey) junk = []byte("JUNK") jwtKeyGood = jwtkey.JWTKey{ID: "ID", PublicKey: publicKey, ExpiresAt: expiresAt} + jwtKeyTaintedGood = jwtkey.JWTKey{ID: "ID", PublicKey: publicKey, ExpiresAt: expiresAt, Tainted: true} jwtKeyNoKeyID = jwtkey.JWTKey{PublicKey: publicKey, ExpiresAt: expiresAt} jwtKeyNoPublicKey = jwtkey.JWTKey{ID: "ID", ExpiresAt: expiresAt} jwtKeyBadPublicKey = jwtkey.JWTKey{ID: "ID", PublicKey: junk, ExpiresAt: expiresAt} jwtKeyNoExpiresAt = jwtkey.JWTKey{ID: "ID", PublicKey: publicKey} pluginGood = &plugintypes.JWTKey{KeyId: "ID", PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix()} + pluginTaintedGood = &plugintypes.JWTKey{KeyId: "ID", PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix(), Tainted: true} pluginNoKeyID = &plugintypes.JWTKey{PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix()} pluginNoPublicKey = &plugintypes.JWTKey{KeyId: "ID", ExpiresAt: expiresAt.Unix()} pluginBadPublicKey = &plugintypes.JWTKey{KeyId: "ID", PublicKey: junk, ExpiresAt: expiresAt.Unix()} pluginNoExpiresAt = &plugintypes.JWTKey{KeyId: "ID", PublicKey: pkixBytes} commonGood = &common.PublicKey{Kid: "ID", PkixBytes: pkixBytes, NotAfter: expiresAt.Unix()} + commonTaintedGood = &common.PublicKey{Kid: "ID", PkixBytes: pkixBytes, NotAfter: expiresAt.Unix(), TaintedKey: true} commonNoKeyID = &common.PublicKey{PkixBytes: pkixBytes, NotAfter: expiresAt.Unix()} commonNoPublicKey = &common.PublicKey{Kid: "ID", NotAfter: expiresAt.Unix()} commonBadPublicKey = &common.PublicKey{Kid: "ID", PkixBytes: junk, NotAfter: expiresAt.Unix()} commonNoExpiresAt = &common.PublicKey{Kid: "ID", PkixBytes: pkixBytes} apiGood = &apitypes.JWTKey{KeyId: "ID", PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix()} + apiTaintedGood = &apitypes.JWTKey{KeyId: "ID", PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix(), Tainted: true} apiNoKeyID = &apitypes.JWTKey{PublicKey: pkixBytes, ExpiresAt: expiresAt.Unix()} apiNoPublicKey = &apitypes.JWTKey{KeyId: "ID", ExpiresAt: expiresAt.Unix()} apiBadPublicKey = &apitypes.JWTKey{KeyId: "ID", PublicKey: junk, ExpiresAt: expiresAt.Unix()} @@ -59,6 +63,7 @@ func TestFromCommonProto(t *testing.T) { } assertOK(t, commonGood, jwtKeyGood) + assertOK(t, commonTaintedGood, jwtKeyTaintedGood) assertFail(t, commonNoKeyID, "missing key ID for JWT key") assertFail(t, commonNoPublicKey, `missing public key for JWT key "ID"`) assertFail(t, commonBadPublicKey, `failed to unmarshal public key for JWT key "ID": `) @@ -80,7 +85,8 @@ func TestFromCommonProtos(t *testing.T) { assert.Panics(t, func() { jwtkey.RequireFromCommonProtos(in) }) } - assertOK(t, []*common.PublicKey{commonGood}, []jwtkey.JWTKey{jwtKeyGood}) + assertOK(t, []*common.PublicKey{commonGood, commonTaintedGood}, + []jwtkey.JWTKey{jwtKeyGood, jwtKeyTaintedGood}) assertFail(t, []*common.PublicKey{commonNoKeyID}, "missing key ID for JWT key") assertOK(t, nil, nil) } @@ -101,6 +107,7 @@ func TestToCommonProto(t *testing.T) { } assertOK(t, jwtKeyGood, commonGood) + assertOK(t, jwtKeyTaintedGood, commonTaintedGood) assertFail(t, jwtKeyNoKeyID, "missing key ID for JWT key") assertFail(t, jwtKeyNoPublicKey, `missing public key for JWT key "ID"`) assertFail(t, jwtKeyBadPublicKey, `failed to marshal public key for JWT key "ID": `) @@ -122,7 +129,8 @@ func TestToCommonProtos(t *testing.T) { assert.Panics(t, func() { jwtkey.RequireToCommonProtos(in) }) } - assertOK(t, []jwtkey.JWTKey{jwtKeyGood}, []*common.PublicKey{commonGood}) + assertOK(t, []jwtkey.JWTKey{jwtKeyGood, jwtKeyTaintedGood}, + []*common.PublicKey{commonGood, commonTaintedGood}) assertFail(t, []jwtkey.JWTKey{jwtKeyNoKeyID}, "missing key ID for JWT key") assertOK(t, nil, nil) } @@ -143,6 +151,7 @@ func TestFromPluginProto(t *testing.T) { } assertOK(t, pluginGood, jwtKeyGood) + assertOK(t, pluginTaintedGood, jwtKeyTaintedGood) assertFail(t, pluginNoKeyID, "missing key ID for JWT key") assertFail(t, pluginNoPublicKey, `missing public key for JWT key "ID"`) assertFail(t, pluginBadPublicKey, `failed to unmarshal public key for JWT key "ID": `) @@ -164,7 +173,8 @@ func TestFromPluginProtos(t *testing.T) { assert.Panics(t, func() { jwtkey.RequireFromPluginProtos(in) }) } - assertOK(t, []*plugintypes.JWTKey{pluginGood}, []jwtkey.JWTKey{jwtKeyGood}) + assertOK(t, []*plugintypes.JWTKey{pluginGood, pluginTaintedGood}, + []jwtkey.JWTKey{jwtKeyGood, jwtKeyTaintedGood}) assertFail(t, []*plugintypes.JWTKey{pluginNoKeyID}, "missing key ID for JWT key") assertOK(t, nil, nil) } @@ -185,6 +195,7 @@ func TestToPluginProto(t *testing.T) { } assertOK(t, jwtKeyGood, pluginGood) + assertOK(t, jwtKeyTaintedGood, pluginTaintedGood) assertFail(t, jwtKeyNoKeyID, "missing key ID for JWT key") assertFail(t, jwtKeyNoPublicKey, `missing public key for JWT key "ID"`) assertFail(t, jwtKeyBadPublicKey, `failed to marshal public key for JWT key "ID": `) @@ -206,7 +217,8 @@ func TestToPluginProtos(t *testing.T) { assert.Panics(t, func() { jwtkey.RequireToPluginProtos(in) }) } - assertOK(t, []jwtkey.JWTKey{jwtKeyGood}, []*plugintypes.JWTKey{pluginGood}) + assertOK(t, []jwtkey.JWTKey{jwtKeyGood, jwtKeyTaintedGood}, + []*plugintypes.JWTKey{pluginGood, pluginTaintedGood}) assertFail(t, []jwtkey.JWTKey{jwtKeyNoKeyID}, "missing key ID for JWT key") assertOK(t, nil, nil) } @@ -227,6 +239,7 @@ func TestToCommonFromPluginProto(t *testing.T) { } assertOK(t, pluginGood, commonGood) + assertOK(t, pluginTaintedGood, commonTaintedGood) assertFail(t, pluginNoKeyID, "missing key ID for JWT key") assertFail(t, pluginNoPublicKey, `missing public key for JWT key "ID"`) assertFail(t, pluginBadPublicKey, `failed to unmarshal public key for JWT key "ID": `) @@ -248,7 +261,8 @@ func TestToCommonFromPluginProtos(t *testing.T) { assert.Panics(t, func() { jwtkey.RequireToCommonFromPluginProtos(in) }) } - assertOK(t, []*plugintypes.JWTKey{pluginGood}, []*common.PublicKey{commonGood}) + assertOK(t, []*plugintypes.JWTKey{pluginGood, pluginTaintedGood}, + []*common.PublicKey{commonGood, commonTaintedGood}) assertFail(t, []*plugintypes.JWTKey{pluginNoKeyID}, "missing key ID for JWT key") assertOK(t, nil, nil) } @@ -269,6 +283,7 @@ func TestToPluginFromCommonProto(t *testing.T) { } assertOK(t, commonGood, pluginGood) + assertOK(t, commonTaintedGood, pluginTaintedGood) assertFail(t, commonNoKeyID, "missing key ID for JWT key") assertFail(t, commonNoPublicKey, `missing public key for JWT key "ID"`) assertFail(t, commonBadPublicKey, `failed to unmarshal public key for JWT key "ID": `) @@ -290,7 +305,8 @@ func TestToPluginFromCommonProtos(t *testing.T) { assert.Panics(t, func() { jwtkey.RequireToPluginFromCommonProtos(in) }) } - assertOK(t, []*common.PublicKey{commonGood}, []*plugintypes.JWTKey{pluginGood}) + assertOK(t, []*common.PublicKey{commonGood, commonTaintedGood}, + []*plugintypes.JWTKey{pluginGood, pluginTaintedGood}) assertFail(t, []*common.PublicKey{commonNoKeyID}, "missing key ID for JWT key") assertOK(t, nil, nil) } @@ -308,6 +324,7 @@ func TestToPluginFromAPIProto(t *testing.T) { } assertOK(t, apiGood, pluginGood) + assertOK(t, apiTaintedGood, pluginTaintedGood) assertFail(t, apiNoKeyID, "missing key ID for JWT key") assertFail(t, apiNoPublicKey, `missing public key for JWT key "ID"`) assertFail(t, apiBadPublicKey, `failed to unmarshal public key for JWT key "ID": `) @@ -328,7 +345,8 @@ func TestToPluginFromAPIProtos(t *testing.T) { assert.Empty(t, actualOut) } - assertOK(t, []*apitypes.JWTKey{apiGood}, []*plugintypes.JWTKey{pluginGood}) + assertOK(t, []*apitypes.JWTKey{apiGood, apiTaintedGood}, + []*plugintypes.JWTKey{pluginGood, pluginTaintedGood}) assertFail(t, []*apitypes.JWTKey{apiNoKeyID}, "missing key ID for JWT key") assertOK(t, nil, nil) } @@ -347,6 +365,7 @@ func TestToAPIProto(t *testing.T) { } assertOK(t, jwtKeyGood, apiGood) + assertOK(t, jwtKeyTaintedGood, apiTaintedGood) assertFail(t, jwtKeyNoKeyID, "missing key ID for JWT key") assertFail(t, jwtKeyNoPublicKey, `missing public key for JWT key "ID"`) assertFail(t, jwtKeyBadPublicKey, `failed to marshal public key for JWT key "ID": `) @@ -367,6 +386,7 @@ func TestToAPIFromPluginProto(t *testing.T) { } assertOK(t, pluginGood, apiGood) + assertOK(t, pluginTaintedGood, apiTaintedGood) assertFail(t, pluginNoKeyID, "missing key ID for JWT key") assertFail(t, pluginNoPublicKey, `missing public key for JWT key "ID"`) assertFail(t, pluginBadPublicKey, `failed to unmarshal public key for JWT key "ID": `) diff --git a/pkg/common/coretypes/jwtkey/plugintypes.go b/pkg/common/coretypes/jwtkey/plugintypes.go index bd8677e8a6..d3699844e7 100644 --- a/pkg/common/coretypes/jwtkey/plugintypes.go +++ b/pkg/common/coretypes/jwtkey/plugintypes.go @@ -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) { @@ -26,7 +26,7 @@ func FromPluginProtos(pbs []*plugintypes.JWTKey) ([]JWTKey, error) { } func ToPluginProto(jwtKey JWTKey) (*plugintypes.JWTKey, error) { - id, publicKey, expiresAt, err := toProtoFields(jwtKey) + id, publicKey, expiresAt, tainted, err := toProtoFields(jwtKey) if err != nil { return nil, err } @@ -34,6 +34,7 @@ func ToPluginProto(jwtKey JWTKey) (*plugintypes.JWTKey, error) { KeyId: id, PublicKey: publicKey, ExpiresAt: expiresAt, + Tainted: tainted, }, nil } @@ -80,7 +81,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 } diff --git a/pkg/common/coretypes/x509certificate/commontypes.go b/pkg/common/coretypes/x509certificate/commontypes.go index 893434ed63..8fbea697cb 100644 --- a/pkg/common/coretypes/x509certificate/commontypes.go +++ b/pkg/common/coretypes/x509certificate/commontypes.go @@ -1,21 +1,19 @@ package x509certificate import ( - "crypto/x509" - plugintypes "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/types" "github.com/spiffe/spire/proto/spire/common" ) -func FromCommonProto(pb *common.Certificate) (*x509.Certificate, error) { - return fromProtoFields(pb.DerBytes) +func FromCommonProto(pb *common.Certificate) (*X509Authority, error) { + return fromProtoFields(pb.DerBytes, pb.TaintedKey) } -func FromCommonProtos(pbs []*common.Certificate) ([]*x509.Certificate, error) { +func FromCommonProtos(pbs []*common.Certificate) ([]*X509Authority, error) { if pbs == nil { return nil, nil } - x509Certificates := make([]*x509.Certificate, 0, len(pbs)) + x509Certificates := make([]*X509Authority, 0, len(pbs)) for _, pb := range pbs { x509Certificate, err := FromCommonProto(pb) if err != nil { @@ -26,23 +24,24 @@ func FromCommonProtos(pbs []*common.Certificate) ([]*x509.Certificate, error) { return x509Certificates, nil } -func ToCommonProto(x509Certificate *x509.Certificate) (*common.Certificate, error) { - asn1, err := toProtoFields(x509Certificate) +func ToCommonProto(x509Authority *X509Authority) (*common.Certificate, error) { + asn1, tainted, err := toProtoFields(x509Authority) if err != nil { return nil, err } return &common.Certificate{ - DerBytes: asn1, + DerBytes: asn1, + TaintedKey: tainted, }, nil } -func ToCommonProtos(x509Certificates []*x509.Certificate) ([]*common.Certificate, error) { - if x509Certificates == nil { +func ToCommonProtos(x509Authorities []*X509Authority) ([]*common.Certificate, error) { + if x509Authorities == nil { return nil, nil } - pbs := make([]*common.Certificate, 0, len(x509Certificates)) - for _, x509Certificate := range x509Certificates { - pb, err := ToCommonProto(x509Certificate) + pbs := make([]*common.Certificate, 0, len(x509Authorities)) + for _, x509Authority := range x509Authorities { + pb, err := ToCommonProto(x509Authority) if err != nil { return nil, err } @@ -58,47 +57,3 @@ func ToCommonFromPluginProtos(pbs []*plugintypes.X509Certificate) ([]*common.Cer } return ToCommonProtos(certs) } - -func RawFromCommonProto(pb *common.Certificate) ([]byte, error) { - return rawFromProtoFields(pb.DerBytes) -} - -func RawFromCommonProtos(pbs []*common.Certificate) ([][]byte, error) { - if pbs == nil { - return nil, nil - } - rawX509Certificates := make([][]byte, 0, len(pbs)) - for _, pb := range pbs { - rawX509Certificate, err := RawFromCommonProto(pb) - if err != nil { - return nil, err - } - rawX509Certificates = append(rawX509Certificates, rawX509Certificate) - } - return rawX509Certificates, nil -} - -func RawToCommonProto(rawX509Certificate []byte) (*common.Certificate, error) { - asn1, err := rawToProtoFields(rawX509Certificate) - if err != nil { - return nil, err - } - return &common.Certificate{ - DerBytes: asn1, - }, nil -} - -func RawToCommonProtos(rawX509Certificates [][]byte) ([]*common.Certificate, error) { - if rawX509Certificates == nil { - return nil, nil - } - pbs := make([]*common.Certificate, 0, len(rawX509Certificates)) - for _, rawX509Certificate := range rawX509Certificates { - pb, err := RawToCommonProto(rawX509Certificate) - if err != nil { - return nil, err - } - pbs = append(pbs, pb) - } - return pbs, nil -} diff --git a/pkg/common/coretypes/x509certificate/plugintypes.go b/pkg/common/coretypes/x509certificate/plugintypes.go index 5b526ef587..7f6eca60a3 100644 --- a/pkg/common/coretypes/x509certificate/plugintypes.go +++ b/pkg/common/coretypes/x509certificate/plugintypes.go @@ -8,15 +8,15 @@ import ( "github.com/spiffe/spire/proto/spire/common" ) -func FromPluginProto(pb *plugintypes.X509Certificate) (*x509.Certificate, error) { - return fromProtoFields(pb.Asn1) +func FromPluginProto(pb *plugintypes.X509Certificate) (*X509Authority, error) { + return fromProtoFields(pb.Asn1, pb.Tainted) } -func FromPluginProtos(pbs []*plugintypes.X509Certificate) ([]*x509.Certificate, error) { +func FromPluginProtos(pbs []*plugintypes.X509Certificate) ([]*X509Authority, error) { if pbs == nil { return nil, nil } - x509Certificates := make([]*x509.Certificate, 0, len(pbs)) + x509Certificates := make([]*X509Authority, 0, len(pbs)) for _, pb := range pbs { x509Certificate, err := FromPluginProto(pb) if err != nil { @@ -27,22 +27,23 @@ func FromPluginProtos(pbs []*plugintypes.X509Certificate) ([]*x509.Certificate, return x509Certificates, nil } -func ToPluginProto(x509Certificate *x509.Certificate) (*plugintypes.X509Certificate, error) { - asn1, err := toProtoFields(x509Certificate) +func ToPluginProto(x509Authority *X509Authority) (*plugintypes.X509Certificate, error) { + asn1, tainted, err := toProtoFields(x509Authority) if err != nil { return nil, err } return &plugintypes.X509Certificate{ - Asn1: asn1, + Asn1: asn1, + Tainted: tainted, }, nil } -func ToPluginProtos(x509Certificates []*x509.Certificate) ([]*plugintypes.X509Certificate, error) { - if x509Certificates == nil { +func ToPluginProtos(x509Authorities []*X509Authority) ([]*plugintypes.X509Certificate, error) { + if x509Authorities == nil { return nil, nil } - pbs := make([]*plugintypes.X509Certificate, 0, len(x509Certificates)) - for _, x509Certificate := range x509Certificates { + pbs := make([]*plugintypes.X509Certificate, 0, len(x509Authorities)) + for _, x509Certificate := range x509Authorities { pb, err := ToPluginProto(x509Certificate) if err != nil { return nil, err @@ -60,61 +61,45 @@ func ToPluginFromCommonProtos(pbs []*common.Certificate) ([]*plugintypes.X509Cer return ToPluginProtos(certs) } -func RawFromPluginProto(pb *plugintypes.X509Certificate) ([]byte, error) { - return rawFromProtoFields(pb.Asn1) -} - -func RawFromPluginProtos(pbs []*plugintypes.X509Certificate) ([][]byte, error) { - if pbs == nil { +func ToPluginFromCertificates(x509Certificates []*x509.Certificate) ([]*plugintypes.X509Certificate, error) { + if x509Certificates == nil { return nil, nil } - rawX509Certificates := make([][]byte, 0, len(pbs)) - for _, pb := range pbs { - rawX509Certificate, err := RawFromPluginProto(pb) + pbs := make([]*plugintypes.X509Certificate, 0, len(x509Certificates)) + for _, eachCert := range x509Certificates { + pb, err := ToPluginFromCertificate(eachCert) if err != nil { return nil, err } - rawX509Certificates = append(rawX509Certificates, rawX509Certificate) + pbs = append(pbs, pb) } - return rawX509Certificates, nil + + return pbs, nil } -func RawToPluginProto(rawX509Certificate []byte) (*plugintypes.X509Certificate, error) { - asn1, err := rawToProtoFields(rawX509Certificate) - if err != nil { +func ToPluginFromCertificate(x509Certificate *x509.Certificate) (*plugintypes.X509Certificate, error) { + if err := validateX509Certificate(x509Certificate); err != nil { return nil, err } + return &plugintypes.X509Certificate{ - Asn1: asn1, + Asn1: x509Certificate.Raw, + Tainted: false, }, nil } -func RawToPluginProtos(rawX509Certificates [][]byte) ([]*plugintypes.X509Certificate, error) { - if rawX509Certificates == nil { - return nil, nil - } - pbs := make([]*plugintypes.X509Certificate, 0, len(rawX509Certificates)) - for _, rawX509Certificate := range rawX509Certificates { - pb, err := RawToPluginProto(rawX509Certificate) - if err != nil { - return nil, err - } - pbs = append(pbs, pb) - } - return pbs, nil -} - func ToPluginFromAPIProto(pb *apitypes.X509Certificate) (*plugintypes.X509Certificate, error) { if pb == nil { return nil, nil } - asn1, err := rawFromProtoFields(pb.Asn1) + x509Authority, err := fromProtoFields(pb.Asn1, pb.Tainted) if err != nil { return nil, err } return &plugintypes.X509Certificate{ - Asn1: asn1, + Asn1: x509Authority.Certificate.Raw, + Tainted: x509Authority.Tainted, }, nil } diff --git a/pkg/common/coretypes/x509certificate/require.go b/pkg/common/coretypes/x509certificate/require.go index 297eb66622..3fbfb404f1 100644 --- a/pkg/common/coretypes/x509certificate/require.go +++ b/pkg/common/coretypes/x509certificate/require.go @@ -7,25 +7,25 @@ import ( "github.com/spiffe/spire/proto/spire/common" ) -func RequireFromCommonProto(pb *common.Certificate) *x509.Certificate { +func RequireFromCommonProto(pb *common.Certificate) *X509Authority { out, err := FromCommonProto(pb) panicOnError(err) return out } -func RequireFromCommonProtos(pbs []*common.Certificate) []*x509.Certificate { +func RequireFromCommonProtos(pbs []*common.Certificate) []*X509Authority { out, err := FromCommonProtos(pbs) panicOnError(err) return out } -func RequireToCommonProto(x509Certificate *x509.Certificate) *common.Certificate { +func RequireToCommonProto(x509Certificate *X509Authority) *common.Certificate { out, err := ToCommonProto(x509Certificate) panicOnError(err) return out } -func RequireToCommonProtos(x509Certificates []*x509.Certificate) []*common.Certificate { +func RequireToCommonProtos(x509Certificates []*X509Authority) []*common.Certificate { out, err := ToCommonProtos(x509Certificates) panicOnError(err) return out @@ -37,25 +37,25 @@ func RequireToCommonFromPluginProtos(pbs []*plugintypes.X509Certificate) []*comm return out } -func RequireFromPluginProto(pb *plugintypes.X509Certificate) *x509.Certificate { +func RequireFromPluginProto(pb *plugintypes.X509Certificate) *X509Authority { out, err := FromPluginProto(pb) panicOnError(err) return out } -func RequireFromPluginProtos(pbs []*plugintypes.X509Certificate) []*x509.Certificate { +func RequireFromPluginProtos(pbs []*plugintypes.X509Certificate) []*X509Authority { out, err := FromPluginProtos(pbs) panicOnError(err) return out } -func RequireToPluginProto(x509Certificate *x509.Certificate) *plugintypes.X509Certificate { +func RequireToPluginProto(x509Certificate *X509Authority) *plugintypes.X509Certificate { out, err := ToPluginProto(x509Certificate) panicOnError(err) return out } -func RequireToPluginProtos(x509Certificates []*x509.Certificate) []*plugintypes.X509Certificate { +func RequireToPluginProtos(x509Certificates []*X509Authority) []*plugintypes.X509Certificate { out, err := ToPluginProtos(x509Certificates) panicOnError(err) return out @@ -67,52 +67,16 @@ func RequireToPluginFromCommonProtos(pbs []*common.Certificate) []*plugintypes.X return out } -func RequireRawFromCommonProto(pb *common.Certificate) []byte { - out, err := RawFromCommonProto(pb) +func RequireToPluginFromCertificates(x509Certificates []*x509.Certificate) []*plugintypes.X509Certificate { + pbs, err := ToPluginFromCertificates(x509Certificates) panicOnError(err) - return out -} - -func RequireRawFromCommonProtos(pbs []*common.Certificate) [][]byte { - out, err := RawFromCommonProtos(pbs) - panicOnError(err) - return out -} - -func RequireRawToCommonProto(rawX509Certificate []byte) *common.Certificate { - out, err := RawToCommonProto(rawX509Certificate) - panicOnError(err) - return out -} - -func RequireRawToCommonProtos(rawX509Certificates [][]byte) []*common.Certificate { - out, err := RawToCommonProtos(rawX509Certificates) - panicOnError(err) - return out + return pbs } -func RequireRawFromPluginProto(pb *plugintypes.X509Certificate) []byte { - out, err := RawFromPluginProto(pb) +func RequireToPluginFromCertificate(x509Certificate *x509.Certificate) *plugintypes.X509Certificate { + pb, err := ToPluginFromCertificate(x509Certificate) panicOnError(err) - return out -} - -func RequireRawFromPluginProtos(pbs []*plugintypes.X509Certificate) [][]byte { - out, err := RawFromPluginProtos(pbs) - panicOnError(err) - return out -} - -func RequireRawToPluginProto(rawX509Certificate []byte) *plugintypes.X509Certificate { - out, err := RawToPluginProto(rawX509Certificate) - panicOnError(err) - return out -} - -func RequireRawToPluginProtos(rawX509Certificates [][]byte) []*plugintypes.X509Certificate { - out, err := RawToPluginProtos(rawX509Certificates) - panicOnError(err) - return out + return pb } func panicOnError(err error) { diff --git a/pkg/common/coretypes/x509certificate/x509certificate.go b/pkg/common/coretypes/x509certificate/x509certificate.go index 4bd2214cf0..604c9de447 100644 --- a/pkg/common/coretypes/x509certificate/x509certificate.go +++ b/pkg/common/coretypes/x509certificate/x509certificate.go @@ -6,7 +6,14 @@ import ( "fmt" ) -func fromProtoFields(asn1 []byte) (*x509.Certificate, error) { +// TODO: may we call it Authority? +// TODO: may we add subjectKeyID? +type X509Authority struct { + Certificate *x509.Certificate + Tainted bool +} + +func fromProtoFields(asn1 []byte, tainted bool) (*X509Authority, error) { if len(asn1) == 0 { return nil, errors.New("missing X.509 certificate data") } @@ -14,24 +21,30 @@ func fromProtoFields(asn1 []byte) (*x509.Certificate, error) { if err != nil { return nil, fmt.Errorf("failed to parse X.509 certificate data: %w", err) } - return x509Certificate, nil + return &X509Authority{ + Certificate: x509Certificate, + Tainted: tainted, + }, nil } -func rawFromProtoFields(asn1 []byte) ([]byte, error) { - cert, err := fromProtoFields(asn1) - if err != nil { - return nil, err +func toProtoFields(x509Authority *X509Authority) ([]byte, bool, error) { + if x509Authority == nil { + return nil, false, errors.New("missing x509 authority") + } + if err := validateX509Certificate(x509Authority.Certificate); err != nil { + return nil, false, err } - return cert.Raw, nil -} -func toProtoFields(x509Certificate *x509.Certificate) ([]byte, error) { - return rawToProtoFields(x509Certificate.Raw) + return x509Authority.Certificate.Raw, x509Authority.Tainted, nil } -func rawToProtoFields(asn1 []byte) ([]byte, error) { - if len(asn1) == 0 { - return nil, errors.New("missing X.509 certificate data") +func validateX509Certificate(cert *x509.Certificate) error { + switch { + case cert == nil: + return errors.New("missing X.509 certificate") + case len(cert.Raw) == 0: + return errors.New("missing X.509 certificate data") + default: + return nil } - return asn1, nil } diff --git a/pkg/common/coretypes/x509certificate/x509certificate_test.go b/pkg/common/coretypes/x509certificate/x509certificate_test.go index ba2b431ec6..d46f2b96f4 100644 --- a/pkg/common/coretypes/x509certificate/x509certificate_test.go +++ b/pkg/common/coretypes/x509certificate/x509certificate_test.go @@ -34,24 +34,32 @@ DgYDVR0PAQH/BAQDAgeAMB8GA1UdIwQYMBaAFAXjxsTxL8UIBZl5lheqqaDOcBhN MAoGCCqGSM49BAMCA0cAMEQCIHf/4m7fPB238z+aPaCuMj019SgA9o3ocdj0yvTx ozrYAiBrdSwMwUG795ZY1D5lh5s0mHb98muSjR3EoPPSiadJtA== -----END CERTIFICATE-----`) - root, _ = pemutil.ParseCertificate(rootPEM) - leaf, _ = pemutil.ParseCertificate(leafPEM) - empty = &x509.Certificate{} - junk = []byte("JUNK") - pluginRoot = &plugintypes.X509Certificate{Asn1: root.Raw} - pluginLeaf = &plugintypes.X509Certificate{Asn1: leaf.Raw} - pluginEmpty = &plugintypes.X509Certificate{} - pluginBad = &plugintypes.X509Certificate{Asn1: junk} - commonRoot = &common.Certificate{DerBytes: root.Raw} - commonLeaf = &common.Certificate{DerBytes: leaf.Raw} - commonEmpty = &common.Certificate{} - commonBad = &common.Certificate{DerBytes: junk} - apiLeaf = &apitypes.X509Certificate{Asn1: leaf.Raw} - apiEmpty = &apitypes.X509Certificate{} + root, _ = pemutil.ParseCertificate(rootPEM) + leaf, _ = pemutil.ParseCertificate(leafPEM) + rootAuthority = &x509certificate.X509Authority{Certificate: root} + leafAuthority = &x509certificate.X509Authority{Certificate: leaf} + leafTaintedAuthority = &x509certificate.X509Authority{Certificate: leaf, Tainted: true} + empty = &x509.Certificate{} + emptyAuthority = &x509certificate.X509Authority{} + emptyAuthorityCert = &x509certificate.X509Authority{Certificate: &x509.Certificate{}} + junk = []byte("JUNK") + pluginRoot = &plugintypes.X509Certificate{Asn1: root.Raw} + pluginLeaf = &plugintypes.X509Certificate{Asn1: leaf.Raw} + pluginTaintedLeaf = &plugintypes.X509Certificate{Asn1: leaf.Raw, Tainted: true} + pluginEmpty = &plugintypes.X509Certificate{} + pluginBad = &plugintypes.X509Certificate{Asn1: junk} + commonRoot = &common.Certificate{DerBytes: root.Raw} + commonLeaf = &common.Certificate{DerBytes: leaf.Raw} + commonTaintedLeaf = &common.Certificate{DerBytes: leaf.Raw, TaintedKey: true} + commonEmpty = &common.Certificate{} + commonBad = &common.Certificate{DerBytes: junk} + apiLeaf = &apitypes.X509Certificate{Asn1: leaf.Raw} + apiTaintedLeaf = &apitypes.X509Certificate{Asn1: leaf.Raw, Tainted: true} + apiEmpty = &apitypes.X509Certificate{} ) func TestFromCommonProto(t *testing.T) { - assertOK := func(t *testing.T, in *common.Certificate, expectOut *x509.Certificate) { + assertOK := func(t *testing.T, in *common.Certificate, expectOut *x509certificate.X509Authority) { actualOut, err := x509certificate.FromCommonProto(in) require.NoError(t, err) assertX509CertificateEqual(t, expectOut, actualOut) @@ -65,13 +73,14 @@ func TestFromCommonProto(t *testing.T) { assert.Panics(t, func() { x509certificate.RequireFromCommonProto(in) }) } - assertOK(t, commonLeaf, leaf) + assertOK(t, commonLeaf, leafAuthority) + assertOK(t, commonTaintedLeaf, leafTaintedAuthority) assertFail(t, commonEmpty, "missing X.509 certificate data") assertFail(t, commonBad, "failed to parse X.509 certificate data: ") } func TestFromCommonProtos(t *testing.T) { - assertOK := func(t *testing.T, in []*common.Certificate, expectOut []*x509.Certificate) { + assertOK := func(t *testing.T, in []*common.Certificate, expectOut []*x509certificate.X509Authority) { actualOut, err := x509certificate.FromCommonProtos(in) require.NoError(t, err) assertX509CertificatesEqual(t, expectOut, actualOut) @@ -85,47 +94,52 @@ func TestFromCommonProtos(t *testing.T) { assert.Panics(t, func() { x509certificate.RequireFromCommonProtos(in) }) } - assertOK(t, []*common.Certificate{commonLeaf, commonRoot}, []*x509.Certificate{leaf, root}) + assertOK(t, []*common.Certificate{commonLeaf, commonRoot}, []*x509certificate.X509Authority{leafAuthority, rootAuthority}) + assertOK(t, []*common.Certificate{commonTaintedLeaf, commonRoot}, []*x509certificate.X509Authority{leafTaintedAuthority, rootAuthority}) assertFail(t, []*common.Certificate{commonEmpty}, "missing X.509 certificate data") assertOK(t, nil, nil) } func TestToCommonProto(t *testing.T) { - assertOK := func(t *testing.T, in *x509.Certificate, expectOut *common.Certificate) { + assertOK := func(t *testing.T, in *x509certificate.X509Authority, expectOut *common.Certificate) { actualOut, err := x509certificate.ToCommonProto(in) require.NoError(t, err) spiretest.AssertProtoEqual(t, expectOut, actualOut) assert.NotPanics(t, func() { spiretest.AssertProtoEqual(t, expectOut, x509certificate.RequireToCommonProto(in)) }) } - assertFail := func(t *testing.T, in *x509.Certificate, expectErr string) { + assertFail := func(t *testing.T, in *x509certificate.X509Authority, expectErr string) { actualOut, err := x509certificate.ToCommonProto(in) spiretest.RequireErrorPrefix(t, err, expectErr) assert.Empty(t, actualOut) assert.Panics(t, func() { x509certificate.RequireToCommonProto(in) }) } - assertOK(t, leaf, commonLeaf) - assertFail(t, empty, "missing X.509 certificate data") + assertOK(t, leafAuthority, commonLeaf) + assertFail(t, nil, "missing x509 authority") + assertFail(t, emptyAuthority, "missing X.509 certificate") + assertFail(t, emptyAuthorityCert, "missing X.509 certificate data") } func TestToCommonProtos(t *testing.T) { - assertOK := func(t *testing.T, in []*x509.Certificate, expectOut []*common.Certificate) { + assertOK := func(t *testing.T, in []*x509certificate.X509Authority, expectOut []*common.Certificate) { actualOut, err := x509certificate.ToCommonProtos(in) require.NoError(t, err) spiretest.AssertProtoListEqual(t, expectOut, actualOut) assert.NotPanics(t, func() { spiretest.AssertProtoListEqual(t, expectOut, x509certificate.RequireToCommonProtos(in)) }) } - assertFail := func(t *testing.T, in []*x509.Certificate, expectErr string) { + assertFail := func(t *testing.T, in []*x509certificate.X509Authority, expectErr string) { actualOut, err := x509certificate.ToCommonProtos(in) spiretest.RequireErrorPrefix(t, err, expectErr) assert.Empty(t, actualOut) assert.Panics(t, func() { x509certificate.RequireToCommonProtos(in) }) } - assertOK(t, []*x509.Certificate{leaf}, []*common.Certificate{commonLeaf}) - assertFail(t, []*x509.Certificate{empty}, "missing X.509 certificate data") + assertOK(t, []*x509certificate.X509Authority{leafAuthority}, []*common.Certificate{commonLeaf}) + assertOK(t, []*x509certificate.X509Authority{leafTaintedAuthority}, []*common.Certificate{commonTaintedLeaf}) + assertFail(t, []*x509certificate.X509Authority{emptyAuthority}, "missing X.509 certificate") + assertFail(t, []*x509certificate.X509Authority{emptyAuthorityCert}, "missing X.509 certificate data") assertOK(t, nil, nil) } @@ -146,13 +160,14 @@ func TestToCommonFromPluginProtos(t *testing.T) { assert.Panics(t, func() { x509certificate.RequireToCommonFromPluginProtos(in) }) } - assertOK(t, []*plugintypes.X509Certificate{pluginLeaf}, []*common.Certificate{commonLeaf}) + assertOK(t, []*plugintypes.X509Certificate{pluginLeaf, pluginTaintedLeaf}, + []*common.Certificate{commonLeaf, commonTaintedLeaf}) assertFail(t, []*plugintypes.X509Certificate{pluginEmpty}, "missing X.509 certificate data") assertOK(t, nil, nil) } func TestFromPluginProto(t *testing.T) { - assertOK := func(t *testing.T, in *plugintypes.X509Certificate, expectOut *x509.Certificate) { + assertOK := func(t *testing.T, in *plugintypes.X509Certificate, expectOut *x509certificate.X509Authority) { actualOut, err := x509certificate.FromPluginProto(in) require.NoError(t, err) assertX509CertificateEqual(t, expectOut, actualOut) @@ -166,13 +181,13 @@ func TestFromPluginProto(t *testing.T) { assert.Panics(t, func() { x509certificate.RequireFromPluginProto(in) }) } - assertOK(t, pluginLeaf, leaf) + assertOK(t, pluginLeaf, leafAuthority) assertFail(t, pluginEmpty, "missing X.509 certificate data") assertFail(t, pluginBad, "failed to parse X.509 certificate data: ") } func TestFromPluginProtos(t *testing.T) { - assertOK := func(t *testing.T, in []*plugintypes.X509Certificate, expectOut []*x509.Certificate) { + assertOK := func(t *testing.T, in []*plugintypes.X509Certificate, expectOut []*x509certificate.X509Authority) { actualOut, err := x509certificate.FromPluginProtos(in) require.NoError(t, err) assertX509CertificatesEqual(t, expectOut, actualOut) @@ -186,47 +201,50 @@ func TestFromPluginProtos(t *testing.T) { assert.Panics(t, func() { x509certificate.RequireFromPluginProtos(in) }) } - assertOK(t, []*plugintypes.X509Certificate{pluginLeaf, pluginRoot}, []*x509.Certificate{leaf, root}) + assertOK(t, []*plugintypes.X509Certificate{pluginLeaf, pluginRoot}, []*x509certificate.X509Authority{leafAuthority, rootAuthority}) assertFail(t, []*plugintypes.X509Certificate{pluginEmpty}, "missing X.509 certificate data") assertOK(t, nil, nil) } func TestToPluginProto(t *testing.T) { - assertOK := func(t *testing.T, in *x509.Certificate, expectOut *plugintypes.X509Certificate) { + assertOK := func(t *testing.T, in *x509certificate.X509Authority, expectOut *plugintypes.X509Certificate) { actualOut, err := x509certificate.ToPluginProto(in) require.NoError(t, err) spiretest.AssertProtoEqual(t, expectOut, actualOut) assert.NotPanics(t, func() { spiretest.AssertProtoEqual(t, expectOut, x509certificate.RequireToPluginProto(in)) }) } - assertFail := func(t *testing.T, in *x509.Certificate, expectErr string) { + assertFail := func(t *testing.T, in *x509certificate.X509Authority, expectErr string) { actualOut, err := x509certificate.ToPluginProto(in) spiretest.RequireErrorPrefix(t, err, expectErr) assert.Empty(t, actualOut) assert.Panics(t, func() { x509certificate.RequireToPluginProto(in) }) } - assertOK(t, leaf, pluginLeaf) - assertFail(t, empty, "missing X.509 certificate data") + assertOK(t, leafAuthority, pluginLeaf) + assertFail(t, nil, "missing x509 authority") + assertFail(t, emptyAuthority, "missing X.509 certificate") + assertFail(t, emptyAuthorityCert, "missing X.509 certificate data") } func TestToPluginProtos(t *testing.T) { - assertOK := func(t *testing.T, in []*x509.Certificate, expectOut []*plugintypes.X509Certificate) { + assertOK := func(t *testing.T, in []*x509certificate.X509Authority, expectOut []*plugintypes.X509Certificate) { actualOut, err := x509certificate.ToPluginProtos(in) require.NoError(t, err) spiretest.AssertProtoListEqual(t, expectOut, actualOut) assert.NotPanics(t, func() { spiretest.AssertProtoListEqual(t, expectOut, x509certificate.RequireToPluginProtos(in)) }) } - assertFail := func(t *testing.T, in []*x509.Certificate, expectErr string) { + assertFail := func(t *testing.T, in []*x509certificate.X509Authority, expectErr string) { actualOut, err := x509certificate.ToPluginProtos(in) spiretest.RequireErrorPrefix(t, err, expectErr) assert.Empty(t, actualOut) assert.Panics(t, func() { x509certificate.RequireToPluginProtos(in) }) } - assertOK(t, []*x509.Certificate{leaf}, []*plugintypes.X509Certificate{pluginLeaf}) - assertFail(t, []*x509.Certificate{empty}, "missing X.509 certificate data") + assertOK(t, []*x509certificate.X509Authority{leafAuthority}, []*plugintypes.X509Certificate{pluginLeaf}) + assertFail(t, []*x509certificate.X509Authority{emptyAuthority}, "missing X.509 certificate") + assertFail(t, []*x509certificate.X509Authority{emptyAuthorityCert}, "missing X.509 certificate data") assertOK(t, nil, nil) } @@ -247,209 +265,89 @@ func TestToPluginFromCommonProtos(t *testing.T) { assert.Panics(t, func() { x509certificate.RequireToPluginFromCommonProtos(in) }) } - assertOK(t, []*common.Certificate{commonLeaf}, []*plugintypes.X509Certificate{pluginLeaf}) + assertOK(t, []*common.Certificate{commonLeaf, commonTaintedLeaf}, []*plugintypes.X509Certificate{pluginLeaf, pluginTaintedLeaf}) assertFail(t, []*common.Certificate{commonEmpty}, "missing X.509 certificate data") assertOK(t, nil, nil) } -func TestRawFromCommonProto(t *testing.T) { - assertOK := func(t *testing.T, in *common.Certificate, expectOut []byte) { - actualOut, err := x509certificate.RawFromCommonProto(in) - require.NoError(t, err) - assert.Equal(t, expectOut, actualOut) - assert.NotPanics(t, func() { assert.Equal(t, expectOut, x509certificate.RequireRawFromCommonProto(in)) }) - } - - assertFail := func(t *testing.T, in *common.Certificate, expectErr string) { - actualOut, err := x509certificate.RawFromCommonProto(in) - spiretest.RequireErrorPrefix(t, err, expectErr) - assert.Empty(t, actualOut) - assert.Panics(t, func() { x509certificate.RequireRawFromCommonProto(in) }) - } - - assertOK(t, commonLeaf, leaf.Raw) - assertFail(t, commonEmpty, "missing X.509 certificate data") - assertFail(t, commonBad, "failed to parse X.509 certificate data: ") -} - -func TestRawFromCommonProtos(t *testing.T) { - assertOK := func(t *testing.T, in []*common.Certificate, expectOut [][]byte) { - actualOut, err := x509certificate.RawFromCommonProtos(in) - require.NoError(t, err) - assert.Equal(t, expectOut, actualOut) - assert.NotPanics(t, func() { assert.Equal(t, expectOut, x509certificate.RequireRawFromCommonProtos(in)) }) - } - - assertFail := func(t *testing.T, in []*common.Certificate, expectErr string) { - actualOut, err := x509certificate.RawFromCommonProtos(in) - spiretest.RequireErrorPrefix(t, err, expectErr) - assert.Empty(t, actualOut) - assert.Panics(t, func() { x509certificate.RequireRawFromCommonProtos(in) }) - } - - assertOK(t, []*common.Certificate{commonLeaf, commonRoot}, [][]byte{leaf.Raw, root.Raw}) - assertFail(t, []*common.Certificate{commonEmpty}, "missing X.509 certificate data") - assertOK(t, nil, nil) -} - -func TestRawToCommonProto(t *testing.T) { - assertOK := func(t *testing.T, in []byte, expectOut *common.Certificate) { - actualOut, err := x509certificate.RawToCommonProto(in) +func TestToPluginFromAPIProto(t *testing.T) { + assertOK := func(t *testing.T, in *apitypes.X509Certificate, expectOut *plugintypes.X509Certificate) { + actualOut, err := x509certificate.ToPluginFromAPIProto(in) require.NoError(t, err) spiretest.AssertProtoEqual(t, expectOut, actualOut) - assert.NotPanics(t, func() { spiretest.AssertProtoEqual(t, expectOut, x509certificate.RequireRawToCommonProto(in)) }) - } - - assertFail := func(t *testing.T, in []byte, expectErr string) { - actualOut, err := x509certificate.RawToCommonProto(in) - spiretest.RequireErrorPrefix(t, err, expectErr) - assert.Empty(t, actualOut) - assert.Panics(t, func() { x509certificate.RequireRawToCommonProto(in) }) - } - - assertOK(t, leaf.Raw, commonLeaf) - assertFail(t, empty.Raw, "missing X.509 certificate data") -} - -func TestRawToCommonProtos(t *testing.T) { - assertOK := func(t *testing.T, in [][]byte, expectOut []*common.Certificate) { - actualOut, err := x509certificate.RawToCommonProtos(in) - require.NoError(t, err) - spiretest.AssertProtoListEqual(t, expectOut, actualOut) - assert.NotPanics(t, func() { spiretest.AssertProtoListEqual(t, expectOut, x509certificate.RequireRawToCommonProtos(in)) }) - } - - assertFail := func(t *testing.T, in [][]byte, expectErr string) { - actualOut, err := x509certificate.RawToCommonProtos(in) - spiretest.RequireErrorPrefix(t, err, expectErr) - assert.Empty(t, actualOut) - assert.Panics(t, func() { x509certificate.RequireRawToCommonProtos(in) }) - } - - assertOK(t, [][]byte{leaf.Raw}, []*common.Certificate{commonLeaf}) - assertFail(t, [][]byte{empty.Raw}, "missing X.509 certificate data") - assertOK(t, nil, nil) -} - -func TestRawFromPluginProto(t *testing.T) { - assertOK := func(t *testing.T, in *plugintypes.X509Certificate, expectOut []byte) { - actualOut, err := x509certificate.RawFromPluginProto(in) - require.NoError(t, err) - assert.Equal(t, expectOut, actualOut) - assert.NotPanics(t, func() { assert.Equal(t, expectOut, x509certificate.RequireRawFromPluginProto(in)) }) - } - - assertFail := func(t *testing.T, in *plugintypes.X509Certificate, expectErr string) { - actualOut, err := x509certificate.RawFromPluginProto(in) - spiretest.RequireErrorPrefix(t, err, expectErr) - assert.Empty(t, actualOut) - assert.Panics(t, func() { x509certificate.RequireRawFromPluginProto(in) }) } - assertOK(t, pluginLeaf, leaf.Raw) - assertFail(t, pluginEmpty, "missing X.509 certificate data") - assertFail(t, pluginBad, "failed to parse X.509 certificate data: ") -} - -func TestRawFromPluginProtos(t *testing.T) { - assertOK := func(t *testing.T, in []*plugintypes.X509Certificate, expectOut [][]byte) { - actualOut, err := x509certificate.RawFromPluginProtos(in) - require.NoError(t, err) - assert.Equal(t, expectOut, actualOut) - assert.NotPanics(t, func() { assert.Equal(t, expectOut, x509certificate.RequireRawFromPluginProtos(in)) }) - } - - assertFail := func(t *testing.T, in []*plugintypes.X509Certificate, expectErr string) { - actualOut, err := x509certificate.RawFromPluginProtos(in) + assertFail := func(t *testing.T, in *apitypes.X509Certificate, expectErr string) { + actualOut, err := x509certificate.ToPluginFromAPIProto(in) spiretest.RequireErrorPrefix(t, err, expectErr) assert.Empty(t, actualOut) - assert.Panics(t, func() { x509certificate.RequireRawFromPluginProtos(in) }) } - assertOK(t, []*plugintypes.X509Certificate{pluginLeaf, pluginRoot}, [][]byte{leaf.Raw, root.Raw}) - assertFail(t, []*plugintypes.X509Certificate{pluginEmpty}, "missing X.509 certificate data") + assertOK(t, apiLeaf, pluginLeaf) + assertOK(t, apiTaintedLeaf, pluginTaintedLeaf) + assertFail(t, apiEmpty, "missing X.509 certificate data") assertOK(t, nil, nil) } -func TestRawToPluginProto(t *testing.T) { - assertOK := func(t *testing.T, in []byte, expectOut *plugintypes.X509Certificate) { - actualOut, err := x509certificate.RawToPluginProto(in) - require.NoError(t, err) - spiretest.AssertProtoEqual(t, expectOut, actualOut) - assert.NotPanics(t, func() { spiretest.AssertProtoEqual(t, expectOut, x509certificate.RequireRawToPluginProto(in)) }) - } - - assertFail := func(t *testing.T, in []byte, expectErr string) { - actualOut, err := x509certificate.RawToPluginProto(in) - spiretest.RequireErrorPrefix(t, err, expectErr) - assert.Empty(t, actualOut) - assert.Panics(t, func() { x509certificate.RequireRawToPluginProto(in) }) - } - - assertOK(t, leaf.Raw, pluginLeaf) - assertFail(t, empty.Raw, "missing X.509 certificate data") -} - -func TestRawToPluginProtos(t *testing.T) { - assertOK := func(t *testing.T, in [][]byte, expectOut []*plugintypes.X509Certificate) { - actualOut, err := x509certificate.RawToPluginProtos(in) +func TestToPluginFromAPIProtos(t *testing.T) { + assertOK := func(t *testing.T, in []*apitypes.X509Certificate, expectOut []*plugintypes.X509Certificate) { + actualOut, err := x509certificate.ToPluginFromAPIProtos(in) require.NoError(t, err) spiretest.AssertProtoListEqual(t, expectOut, actualOut) - assert.NotPanics(t, func() { spiretest.AssertProtoListEqual(t, expectOut, x509certificate.RequireRawToPluginProtos(in)) }) } - assertFail := func(t *testing.T, in [][]byte, expectErr string) { - actualOut, err := x509certificate.RawToPluginProtos(in) + assertFail := func(t *testing.T, in []*apitypes.X509Certificate, expectErr string) { + actualOut, err := x509certificate.ToPluginFromAPIProtos(in) spiretest.RequireErrorPrefix(t, err, expectErr) assert.Empty(t, actualOut) - assert.Panics(t, func() { x509certificate.RequireRawToPluginProtos(in) }) } - assertOK(t, [][]byte{leaf.Raw}, []*plugintypes.X509Certificate{pluginLeaf}) - assertFail(t, [][]byte{empty.Raw}, "missing X.509 certificate data") + assertOK(t, []*apitypes.X509Certificate{apiLeaf, apiTaintedLeaf}, + []*plugintypes.X509Certificate{pluginLeaf, pluginTaintedLeaf}) + assertFail(t, []*apitypes.X509Certificate{apiEmpty}, "missing X.509 certificate data") assertOK(t, nil, nil) } -func TestToPluginFromAPIProto(t *testing.T) { - assertOK := func(t *testing.T, in *apitypes.X509Certificate, expectOut *plugintypes.X509Certificate) { - actualOut, err := x509certificate.ToPluginFromAPIProto(in) +func TestToPluginFromCertificate(t *testing.T) { + assertOK := func(t *testing.T, in *x509.Certificate, expectOut *plugintypes.X509Certificate) { + actualOut, err := x509certificate.ToPluginFromCertificate(in) require.NoError(t, err) spiretest.AssertProtoEqual(t, expectOut, actualOut) } - assertFail := func(t *testing.T, in *apitypes.X509Certificate, expectErr string) { - actualOut, err := x509certificate.ToPluginFromAPIProto(in) + assertFail := func(t *testing.T, in *x509.Certificate, expectErr string) { + actualOut, err := x509certificate.ToPluginFromCertificate(in) spiretest.RequireErrorPrefix(t, err, expectErr) assert.Empty(t, actualOut) } - assertOK(t, apiLeaf, pluginLeaf) - assertFail(t, apiEmpty, "missing X.509 certificate data") - assertOK(t, nil, nil) + assertOK(t, leaf, pluginLeaf) + assertFail(t, nil, "missing X.509 certificate") + assertFail(t, empty, "missing X.509 certificate data") } -func TestToPluginFromAPIProtos(t *testing.T) { - assertOK := func(t *testing.T, in []*apitypes.X509Certificate, expectOut []*plugintypes.X509Certificate) { - actualOut, err := x509certificate.ToPluginFromAPIProtos(in) +func TestToPluginFromCertificates(t *testing.T) { + assertOK := func(t *testing.T, in []*x509.Certificate, expectOut []*plugintypes.X509Certificate) { + actualOut, err := x509certificate.ToPluginFromCertificates(in) require.NoError(t, err) spiretest.AssertProtoListEqual(t, expectOut, actualOut) } - assertFail := func(t *testing.T, in []*apitypes.X509Certificate, expectErr string) { - actualOut, err := x509certificate.ToPluginFromAPIProtos(in) + assertFail := func(t *testing.T, in []*x509.Certificate, expectErr string) { + actualOut, err := x509certificate.ToPluginFromCertificates(in) spiretest.RequireErrorPrefix(t, err, expectErr) assert.Empty(t, actualOut) } - assertOK(t, []*apitypes.X509Certificate{apiLeaf}, []*plugintypes.X509Certificate{pluginLeaf}) - assertFail(t, []*apitypes.X509Certificate{apiEmpty}, "missing X.509 certificate data") + assertOK(t, []*x509.Certificate{leaf}, + []*plugintypes.X509Certificate{pluginLeaf}) + assertFail(t, []*x509.Certificate{empty}, "missing X.509 certificate data") assertOK(t, nil, nil) } - -func assertX509CertificatesEqual(t *testing.T, expected, actual []*x509.Certificate) { +func assertX509CertificatesEqual(t *testing.T, expected, actual []*x509certificate.X509Authority) { assert.Empty(t, cmp.Diff(expected, actual)) } -func assertX509CertificateEqual(t *testing.T, expected, actual *x509.Certificate) { +func assertX509CertificateEqual(t *testing.T, expected, actual *x509certificate.X509Authority) { assert.Empty(t, cmp.Diff(expected, actual)) } diff --git a/pkg/common/telemetry/names.go b/pkg/common/telemetry/names.go index e94765de7c..4e40260ee3 100644 --- a/pkg/common/telemetry/names.go +++ b/pkg/common/telemetry/names.go @@ -541,6 +541,9 @@ const ( // with other tags to add clarity Subject = "subject" + // SubjectKeyID 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" @@ -599,6 +602,9 @@ const ( // with other tags to add clarity Updated = "updated" + // UpstreamAuthorityID tags a signing authority ID + UpstreamAuthorityID = "upstream_authority_id" + // StoreSvid tags if entry is storable StoreSvid = "store_svid" diff --git a/pkg/common/telemetry/server/datastore/wrapper.go b/pkg/common/telemetry/server/datastore/wrapper.go index 82ed815f94..96f84bd0b0 100644 --- a/pkg/common/telemetry/server/datastore/wrapper.go +++ b/pkg/common/telemetry/server/datastore/wrapper.go @@ -2,7 +2,6 @@ package datastore import ( "context" - "crypto" "time" "github.com/spiffe/go-spiffe/v2/spiffeid" @@ -264,16 +263,16 @@ 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) { +func (w metricsWrapper) RevokeX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToRevoke string) (err error) { callCounter := StartRevokeX509CACall(w.m) defer callCounter.Done(&err) - return w.ds.RevokeX509CA(ctx, trustDomainID, publicKeyToRevoke) + return w.ds.RevokeX509CA(ctx, trustDomainID, subjectKeyIDToRevoke) } func (w metricsWrapper) TaintJWTKey(ctx context.Context, trustDomainID string, authorityID string) (_ *common.PublicKey, err error) { diff --git a/pkg/common/telemetry/server/datastore/wrapper_test.go b/pkg/common/telemetry/server/datastore/wrapper_test.go index 72e47349f6..a0b44885a2 100644 --- a/pkg/common/telemetry/server/datastore/wrapper_test.go +++ b/pkg/common/telemetry/server/datastore/wrapper_test.go @@ -2,7 +2,6 @@ package datastore import ( "context" - "crypto" "errors" "reflect" "strings" @@ -490,11 +489,11 @@ func (ds *fakeDataStore) SetBundle(context.Context, *common.Bundle) (*common.Bun return &common.Bundle{}, ds.err } -func (ds *fakeDataStore) TaintX509CA(context.Context, string, crypto.PublicKey) error { +func (ds *fakeDataStore) TaintX509CA(context.Context, string, string) error { return ds.err } -func (ds *fakeDataStore) RevokeX509CA(context.Context, string, crypto.PublicKey) error { +func (ds *fakeDataStore) RevokeX509CA(context.Context, string, string) error { return ds.err } diff --git a/pkg/server/api/bundle.go b/pkg/server/api/bundle.go index 940cbc58ac..c89e4729f8 100644 --- a/pkg/server/api/bundle.go +++ b/pkg/server/api/bundle.go @@ -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, }) } @@ -50,6 +51,7 @@ func PublicKeysToProto(keys []*common.PublicKey) []*types.JWTKey { PublicKey: key.PkixBytes, KeyId: key.Kid, ExpiresAt: key.NotAfter, + Tainted: key.TaintedKey, }) } return jwtAuthorities diff --git a/pkg/server/api/bundle_test.go b/pkg/server/api/bundle_test.go index dad2852822..64007140e9 100644 --- a/pkg/server/api/bundle_test.go +++ b/pkg/server/api/bundle_test.go @@ -31,13 +31,22 @@ func TestBundleToProto(t *testing.T) { TrustDomainId: td.IDString(), RefreshHint: 10, SequenceNumber: 42, - RootCas: []*common.Certificate{{DerBytes: []byte("cert-bytes")}}, + RootCas: []*common.Certificate{ + {DerBytes: []byte("cert-bytes")}, + {DerBytes: []byte("tainted-cert"), TaintedKey: true}, + }, JwtSigningKeys: []*common.PublicKey{ { Kid: "key-id-1", NotAfter: 1590514224, PkixBytes: []byte("pkix key"), }, + { + Kid: "key-id-2", + NotAfter: 1590514224, + PkixBytes: []byte("pkix key"), + TaintedKey: true, + }, }, }, expectBundle: &types.Bundle{ @@ -48,6 +57,10 @@ func TestBundleToProto(t *testing.T) { { Asn1: []byte("cert-bytes"), }, + { + Asn1: []byte("tainted-cert"), + Tainted: true, + }, }, JwtAuthorities: []*types.JWTKey{ { @@ -56,6 +69,12 @@ func TestBundleToProto(t *testing.T) { KeyId: "key-id-1", ExpiresAt: 1590514224, }, + { + PublicKey: []byte("pkix key"), + KeyId: "key-id-2", + ExpiresAt: 1590514224, + Tainted: true, + }, }, }, }, diff --git a/pkg/server/api/localauthority/v1/service.go b/pkg/server/api/localauthority/v1/service.go index b8e8978d42..97f8af987d 100644 --- a/pkg/server/api/localauthority/v1/service.go +++ b/pkg/server/api/localauthority/v1/service.go @@ -2,16 +2,14 @@ package localauthority import ( "context" - "crypto" - "crypto/x509" "errors" "fmt" + "strings" "github.com/sirupsen/logrus" "github.com/spiffe/go-spiffe/v2/spiffeid" localauthorityv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/localauthority/v1" "github.com/spiffe/spire/pkg/common/telemetry" - "github.com/spiffe/spire/pkg/common/x509util" "github.com/spiffe/spire/pkg/server/api" "github.com/spiffe/spire/pkg/server/api/rpccontext" "github.com/spiffe/spire/pkg/server/ca/manager" @@ -33,6 +31,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. @@ -335,6 +335,10 @@ func (s *Service) TaintX509Authority(ctx context.Context, req *localauthorityv1. 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 authority", nil) + } + nextSlot := s.ca.GetNextX509CASlot() switch { @@ -355,7 +359,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) } @@ -371,25 +375,58 @@ 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, buildAuditUpstreamLogFields(req.SubjectKeyId)) + log := rpccontext.Logger(ctx) + + if req.SubjectKeyId != "" { + log = log.WithField(telemetry.SubjectKeyID, req.SubjectKeyId) + } + + if !s.ca.IsUpstreamAuthority() { + return nil, api.MakeErr(log, codes.FailedPrecondition, "upstream authority is not configured", nil) + } + + // TODO: may we request in lower case? + // Normalize SKID + subjectKeyIDRequest := strings.ToLower(req.SubjectKeyId) + if err := s.validateUpstreamAuthoritySubjectKey(subjectKeyIDRequest); err != nil { + return nil, api.MakeErr(log, codes.InvalidArgument, "provided subject key id is not valid", err) + } + + if err := s.ds.TaintX509CA(ctx, s.td.IDString(), subjectKeyIDRequest); err != nil { + return nil, api.MakeErr(log, codes.Internal, "failed to taint upstream authority", err) + } + + rpccontext.AuditRPC(ctx) + log.Info("X.509 upstream authority tainted successfully") + + 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) - authorityID, publicKey, err := s.getX509PublicKey(ctx, req.AuthorityId) - if err != nil { - if req.AuthorityId != "" { - log = log.WithField(telemetry.LocalAuthorityID, req.AuthorityId) - } + 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 revoked if there is an upstream authority", nil) + } + + if err := s.validateLocalAuthorityID(req.AuthorityId); err != nil { return nil, api.MakeErr(log, codes.InvalidArgument, "invalid authority ID", err) } - log = log.WithField(telemetry.LocalAuthorityID, authorityID) - if err := s.ds.RevokeX509CA(ctx, s.td.IDString(), publicKey); err != nil { + log = log.WithField(telemetry.LocalAuthorityID, req.AuthorityId) + if err := s.ds.RevokeX509CA(ctx, s.td.IDString(), req.AuthorityId); err != nil { return nil, api.MakeErr(log, codes.Internal, "failed to revoke X.509 authority", err) } state := &localauthorityv1.AuthorityState{ - AuthorityId: authorityID, + AuthorityId: req.AuthorityId, } rpccontext.AuditRPC(ctx) @@ -400,44 +437,72 @@ func (s *Service) RevokeX509Authority(ctx context.Context, req *localauthorityv1 }, nil } -// getX509PublicKey validates provided authority ID, and return OLD associated public key -func (s *Service) getX509PublicKey(ctx context.Context, authorityID string) (string, crypto.PublicKey, error) { - if authorityID == "" { - return "", nil, errors.New("no authority ID provided") +func (s *Service) RevokeX509UpstreamAuthority(ctx context.Context, req *localauthorityv1.RevokeX509UpstreamAuthorityRequest) (*localauthorityv1.RevokeX509UpstreamAuthorityResponse, error) { + rpccontext.AddRPCAuditFields(ctx, buildAuditUpstreamLogFields(req.SubjectKeyId)) + log := rpccontext.Logger(ctx) + + if req.SubjectKeyId != "" { + log = log.WithField(telemetry.SubjectKeyID, req.SubjectKeyId) } + if !s.ca.IsUpstreamAuthority() { + return nil, api.MakeErr(log, codes.FailedPrecondition, "upstream authority is not configured", nil) + } + + // TODO: may we request in lower case? + // Normalize SKID + subjectKeyIDRequest := strings.ToLower(req.SubjectKeyId) + if err := s.validateUpstreamAuthoritySubjectKey(subjectKeyIDRequest); err != nil { + return nil, api.MakeErr(log, codes.InvalidArgument, "invalid subject key ID", err) + } + + if err := s.ds.RevokeX509CA(ctx, s.td.IDString(), subjectKeyIDRequest); err != nil { + return nil, api.MakeErr(log, codes.Internal, "failed to revoke X.509 upstream authority", err) + } + + rpccontext.AuditRPC(ctx) + log.Info("X.509 upstream authority successfully revoked") + + return &localauthorityv1.RevokeX509UpstreamAuthorityResponse{}, nil +} + +// validateLocalAuthorityID validates provided authority ID, and return OLD associated public key +func (s *Service) validateLocalAuthorityID(authorityID string) error { nextSlot := s.ca.GetNextX509CASlot() - if authorityID == nextSlot.AuthorityID() { - if nextSlot.Status() == journal.Status_PREPARED { - return "", nil, errors.New("unable to use a prepared key") - } + switch { + case authorityID == "": + return errors.New("no authority ID provided") + case authorityID == s.ca.GetCurrentX509CASlot().AuthorityID(): + return errors.New("unable to use current authority") + case authorityID != nextSlot.AuthorityID(): + return errors.New("only Old local authority can be revoked") + case nextSlot.Status() != journal.Status_OLD: + return errors.New("only Old local authority can be revoked") + } + + return nil +} - return nextSlot.AuthorityID(), nextSlot.PublicKey(), nil +func (s *Service) validateUpstreamAuthoritySubjectKey(subjectKeyIDRequest string) error { + if subjectKeyIDRequest == "" { + return errors.New("no subject key ID provided") } currentSlot := s.ca.GetCurrentX509CASlot() - if currentSlot.AuthorityID() == authorityID { - return "", nil, errors.New("unable to use current authority") + if subjectKeyIDRequest == currentSlot.UpstreamAuthorityID() { + return errors.New("unable to use upstream authority singing current authority") } - bundle, err := s.ds.FetchBundle(ctx, s.td.IDString()) - if err != nil { - return "", nil, err + nextSlot := s.ca.GetNextX509CASlot() + if subjectKeyIDRequest != nextSlot.UpstreamAuthorityID() { + return errors.New("upstream authority didn't sign the old local authority") } - for _, ca := range bundle.RootCas { - cert, err := x509.ParseCertificate(ca.DerBytes) - if err != nil { - return "", nil, err - } - - subjectKeyID := x509util.SubjectKeyIDToString(cert.SubjectKeyId) - if authorityID == subjectKeyID { - return subjectKeyID, cert.PublicKey, nil - } + if nextSlot.Status() == journal.Status_PREPARED { + return errors.New("only upstream authorities signing an old authority can be used") } - return "", nil, errors.New("no ca found with provided authority ID") + return nil } // validateAuthorityID validates provided authority ID @@ -482,6 +547,14 @@ func buildAuditLogFields(authorityID string) logrus.Fields { return fields } +func buildAuditUpstreamLogFields(authorityID string) logrus.Fields { + fields := logrus.Fields{} + if authorityID != "" { + fields[telemetry.SubjectKeyID] = authorityID + } + return fields +} + func stateFromSlot(s manager.Slot) *localauthorityv1.AuthorityState { return &localauthorityv1.AuthorityState{ AuthorityId: s.AuthorityID(), diff --git a/pkg/server/api/localauthority/v1/service_test.go b/pkg/server/api/localauthority/v1/service_test.go index edafbe1657..badce0565f 100644 --- a/pkg/server/api/localauthority/v1/service_test.go +++ b/pkg/server/api/localauthority/v1/service_test.go @@ -24,6 +24,7 @@ import ( "github.com/spiffe/spire/test/fakes/fakedatastore" "github.com/spiffe/spire/test/grpctest" "github.com/spiffe/spire/test/spiretest" + "github.com/spiffe/spire/test/testca" "github.com/spiffe/spire/test/testkey" testutil "github.com/spiffe/spire/test/util" "github.com/stretchr/testify/require" @@ -473,7 +474,6 @@ func TestTaintJWTAuthority(t *testing.T) { nextSlot *fakeSlot keyToTaint string - expectKeyToTaint string expectLogs []spiretest.LogEntry expectCode codes.Code expectMsg string @@ -709,11 +709,10 @@ func TestRevokeJWTAuthority(t *testing.T) { keyToRevoke string noTaintedKeys bool - expectKeyToTaint crypto.PublicKey - expectLogs []spiretest.LogEntry - expectCode codes.Code - expectMsg string - expectResp *localauthorityv1.RevokeJWTAuthorityResponse + expectLogs []spiretest.LogEntry + expectCode codes.Code + expectMsg string + expectResp *localauthorityv1.RevokeJWTAuthorityResponse }{ { name: "revoke authority from parameter", @@ -1299,8 +1298,6 @@ func TestTaintX509Authority(t *testing.T) { nextCA, nextKey, err := testutil.SelfSign(template) require.NoError(t, err) - nextPublicKeyRaw, err := x509.MarshalPKIXPublicKey(nextKey.Public()) - require.NoError(t, err) nextKeySKI, err := x509util.GetSubjectKeyID(nextKey.Public()) require.NoError(t, err) nextAuthorityID := x509util.SubjectKeyIDToString(nextKeySKI) @@ -1308,18 +1305,30 @@ func TestTaintX509Authority(t *testing.T) { oldCA, _, err := testutil.SelfSign(template) require.NoError(t, err) - for _, tt := range []struct { - name string - currentSlot *fakeSlot - nextSlot *fakeSlot - keyToTaint string + defaultRootCAs := []*common.Certificate{ + { + DerBytes: currentCA.Raw, + }, + { + DerBytes: nextCA.Raw, + }, + { + DerBytes: oldCA.Raw, + }, + } - expectKeyToTaint crypto.PublicKey - expectLogs []spiretest.LogEntry - expectCode codes.Code - expectMsg string - expectResp *localauthorityv1.TaintX509AuthorityResponse - taintedKey []byte + for _, tt := range []struct { + name string + currentSlot *fakeSlot + nextSlot *fakeSlot + keyToTaint string + customRootCAs []*common.Certificate + isUpstreamAuthority bool + + expectLogs []spiretest.LogEntry + expectCode codes.Code + expectMsg string + expectResp *localauthorityv1.TaintX509AuthorityResponse }{ { name: "taint old authority", @@ -1463,9 +1472,20 @@ func TestTaintX509Authority(t *testing.T) { currentSlot: createSlot(journal.Status_ACTIVE, currentAuthorityID, currentKey.Public(), notAfterCurrent), nextSlot: createSlot(journal.Status_OLD, nextAuthorityID, nextKey.Public(), notAfterNext), keyToTaint: nextAuthorityID, - taintedKey: nextPublicKeyRaw, - expectCode: codes.Internal, - expectMsg: "failed to taint X.509 authority: root CA is already tainted", + customRootCAs: []*common.Certificate{ + { + DerBytes: currentCA.Raw, + }, + { + DerBytes: nextCA.Raw, + TaintedKey: true, + }, + { + DerBytes: oldCA.Raw, + }, + }, + expectCode: codes.Internal, + expectMsg: "failed to taint X.509 authority: root CA is already tainted", expectLogs: []spiretest.LogEntry{ { Level: logrus.ErrorLevel, @@ -1488,37 +1508,333 @@ func TestTaintX509Authority(t *testing.T) { }, }, }, + { + name: "fail on upstream authority", + currentSlot: createSlot(journal.Status_ACTIVE, currentAuthorityID, currentKey.Public(), notAfterCurrent), + nextSlot: createSlot(journal.Status_OLD, nextAuthorityID, nextKey.Public(), notAfterNext), + keyToTaint: nextAuthorityID, + isUpstreamAuthority: true, + expectCode: codes.FailedPrecondition, + expectMsg: "local authority can't be tainted if there is an upstream authority", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Local authority can't be tainted if there is an upstream authority", + Data: logrus.Fields{ + telemetry.LocalAuthorityID: nextAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "FailedPrecondition", + telemetry.StatusMessage: "local authority can't be tainted if there is an upstream authority", + telemetry.Type: "audit", + telemetry.LocalAuthorityID: nextAuthorityID, + }, + }, + }, + }, } { t.Run(tt.name, func(t *testing.T) { test := setupServiceTest(t) defer test.Cleanup() - var taintedKeys []*common.X509TaintedKey - if tt.taintedKey != nil { - taintedKeys = append(taintedKeys, &common.X509TaintedKey{PublicKey: tt.taintedKey}) - } - test.ca.currentX509CASlot = tt.currentSlot test.ca.nextX509CASlot = tt.nextSlot + test.ca.isUpstreamAuthority = tt.isUpstreamAuthority + + rootCAs := defaultRootCAs + if tt.customRootCAs != nil { + rootCAs = tt.customRootCAs + } + _, err := test.ds.CreateBundle(ctx, &common.Bundle{ TrustDomainId: serverTrustDomain.IDString(), - RootCas: []*common.Certificate{ - { - DerBytes: currentCA.Raw, + RootCas: rootCAs, + }) + require.NoError(t, err) + + resp, err := test.client.TaintX509Authority(ctx, &localauthorityv1.TaintX509AuthorityRequest{ + AuthorityId: tt.keyToTaint, + }) + + spiretest.AssertGRPCStatusHasPrefix(t, err, tt.expectCode, tt.expectMsg) + spiretest.AssertProtoEqual(t, tt.expectResp, resp) + spiretest.AssertLogs(t, test.logHook.AllEntries(), tt.expectLogs) + }) + } +} + +func TestTaintX509UpstreamAuthority(t *testing.T) { + getUpstreamCertAndSubjectID := func(ca *testca.CA) (*x509.Certificate, string) { + // Self signed CA will return itself + cert := ca.X509Authorities()[0] + return cert, x509util.SubjectKeyIDToString(cert.SubjectKeyId) + } + + // Create active upstream authority + activeUpstreamAuthority := testca.New(t, serverTrustDomain) + activeUpstreamAuthorityCert, activeUpstreamAuthorityID := getUpstreamCertAndSubjectID(activeUpstreamAuthority) + + // Create newUpstreamAuthority childs + currentIntermediateCA := activeUpstreamAuthority.ChildCA(testca.WithID(serverTrustDomain.ID())) + nextIntermediateCA := activeUpstreamAuthority.ChildCA(testca.WithID(serverTrustDomain.ID())) + + // Create old upstream authority + deactivatedUpstreamAuthority := testca.New(t, serverTrustDomain) + deactivatedUpstreamAuthorityCert, deactivatedUpstreamAuthorityID := getUpstreamCertAndSubjectID(deactivatedUpstreamAuthority) + + // Create intermediate using old upstream authority + oldIntermediateCA := deactivatedUpstreamAuthority.ChildCA(testca.WithID(serverTrustDomain.ID())) + + defaultRootCAs := []*common.Certificate{ + { + DerBytes: activeUpstreamAuthorityCert.Raw, + }, + { + DerBytes: deactivatedUpstreamAuthorityCert.Raw, + }, + } + + for _, tt := range []struct { + name string + currentSlot *fakeSlot + nextSlot *fakeSlot + subjectKeyIDToTaint string + customRootCAs []*common.Certificate + isLocalAuthority bool + + expectLogs []spiretest.LogEntry + expectCode codes.Code + expectMsg string + expectResp *localauthorityv1.TaintX509UpstreamAuthorityResponse + }{ + { + name: "taint old upstream authority", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, oldIntermediateCA, notAfterNext), + subjectKeyIDToTaint: deactivatedUpstreamAuthorityID, + expectResp: &localauthorityv1.TaintX509UpstreamAuthorityResponse{}, + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "success", + telemetry.Type: "audit", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, }, - { - DerBytes: nextCA.Raw, + }, + { + Level: logrus.InfoLevel, + Message: "X.509 upstream authority tainted successfully", + Data: logrus.Fields{ + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, }, - { - DerBytes: oldCA.Raw, + }, + }, + }, + { + name: "unable to taint with upstream disabled", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, oldIntermediateCA, notAfterNext), + subjectKeyIDToTaint: deactivatedUpstreamAuthorityID, + expectCode: codes.FailedPrecondition, + expectMsg: "upstream authority is not configured", + isLocalAuthority: true, + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Upstream authority is not configured", + Data: logrus.Fields{ + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "FailedPrecondition", + telemetry.StatusMessage: "upstream authority is not configured", + telemetry.Type: "audit", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, }, }, - X509TaintedKeys: taintedKeys, + }, + }, + { + name: "no subjectID provided", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, oldIntermediateCA, notAfterNext), + expectCode: codes.InvalidArgument, + expectMsg: "provided subject key id is not valid: no subject key ID provided", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Invalid argument: provided subject key id is not valid", + Data: logrus.Fields{ + logrus.ErrorKey: "no subject key ID provided", + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "InvalidArgument", + telemetry.StatusMessage: "provided subject key id is not valid: no subject key ID provided", + telemetry.Type: "audit", + }, + }, + }, + }, + { + name: "unable to use active upstream authority", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, nextIntermediateCA, notAfterNext), + subjectKeyIDToTaint: activeUpstreamAuthorityID, + expectCode: codes.InvalidArgument, + expectMsg: "provided subject key id is not valid: unable to use upstream authority singing current authority", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Invalid argument: provided subject key id is not valid", + Data: logrus.Fields{ + logrus.ErrorKey: "unable to use upstream authority singing current authority", + telemetry.SubjectKeyID: activeUpstreamAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "InvalidArgument", + telemetry.StatusMessage: "provided subject key id is not valid: unable to use upstream authority singing current authority", + telemetry.Type: "audit", + telemetry.SubjectKeyID: activeUpstreamAuthorityID, + }, + }, + }, + }, + { + name: "unknown subjectKeyID", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, nextIntermediateCA, notAfterNext), + subjectKeyIDToTaint: "invalidID", + expectCode: codes.InvalidArgument, + expectMsg: "provided subject key id is not valid: upstream authority didn't sign the old local authority", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Invalid argument: provided subject key id is not valid", + Data: logrus.Fields{ + logrus.ErrorKey: "upstream authority didn't sign the old local authority", + telemetry.SubjectKeyID: "invalidID", + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "InvalidArgument", + telemetry.StatusMessage: "provided subject key id is not valid: upstream authority didn't sign the old local authority", + telemetry.Type: "audit", + telemetry.SubjectKeyID: "invalidID", + }, + }, + }, + }, + { + name: "prepared authority signed by upstream authority", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_PREPARED, oldIntermediateCA, notAfterNext), + subjectKeyIDToTaint: deactivatedUpstreamAuthorityID, + expectCode: codes.InvalidArgument, + expectMsg: "provided subject key id is not valid: only upstream authorities signing an old authority can be used", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Invalid argument: provided subject key id is not valid", + Data: logrus.Fields{ + logrus.ErrorKey: "only upstream authorities signing an old authority can be used", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "InvalidArgument", + telemetry.StatusMessage: "provided subject key id is not valid: only upstream authorities signing an old authority can be used", + telemetry.Type: "audit", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + }, + }, + { + name: "ds failed to taint", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, oldIntermediateCA, notAfterNext), + subjectKeyIDToTaint: deactivatedUpstreamAuthorityID, + expectCode: codes.Internal, + expectMsg: "failed to taint upstream authority: no ca found with provided subject key ID", + customRootCAs: []*common.Certificate{ + { + DerBytes: activeUpstreamAuthorityCert.Raw, + }, + }, + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Failed to taint upstream authority", + Data: logrus.Fields{ + logrus.ErrorKey: "rpc error: code = NotFound desc = no ca found with provided subject key ID", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "Internal", + telemetry.StatusMessage: "failed to taint upstream authority: no ca found with provided subject key ID", + telemetry.Type: "audit", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + test := setupServiceTest(t) + defer test.Cleanup() + + test.ca.currentX509CASlot = tt.currentSlot + test.ca.nextX509CASlot = tt.nextSlot + test.ca.isUpstreamAuthority = !tt.isLocalAuthority + + rootCAs := defaultRootCAs + if tt.customRootCAs != nil { + rootCAs = tt.customRootCAs + } + + _, err := test.ds.CreateBundle(ctx, &common.Bundle{ + TrustDomainId: serverTrustDomain.IDString(), + RootCas: rootCAs, }) require.NoError(t, err) - resp, err := test.client.TaintX509Authority(ctx, &localauthorityv1.TaintX509AuthorityRequest{ - AuthorityId: tt.keyToTaint, + resp, err := test.client.TaintX509UpstreamAuthority(ctx, &localauthorityv1.TaintX509UpstreamAuthorityRequest{ + SubjectKeyId: tt.subjectKeyIDToTaint, }) spiretest.AssertGRPCStatusHasPrefix(t, err, tt.expectCode, tt.expectMsg) @@ -1542,41 +1858,37 @@ func TestRevokeX509Authority(t *testing.T) { nextCA, nextKey, err := testutil.SelfSign(template) require.NoError(t, err) - nextPublicKeyRaw, err := x509.MarshalPKIXPublicKey(nextKey.Public()) - require.NoError(t, err) nextKeySKI, err := x509util.GetSubjectKeyID(nextKey.Public()) require.NoError(t, err) nextAuthorityID := x509util.SubjectKeyIDToString(nextKeySKI) - oldCA, oldKey, err := testutil.SelfSign(template) + _, noStoredKey, err := testutil.SelfSign(template) require.NoError(t, err) - oldPublicKeyRaw, err := x509.MarshalPKIXPublicKey(oldKey.Public()) - require.NoError(t, err) - oldKeySKI, err := x509util.GetSubjectKeyID(oldKey.Public()) + noStoredKeySKI, err := x509util.GetSubjectKeyID(noStoredKey.Public()) require.NoError(t, err) - oldAuthorityID := x509util.SubjectKeyIDToString(oldKeySKI) + noStoredAuthorityID := x509util.SubjectKeyIDToString(noStoredKeySKI) for _, tt := range []struct { - name string - currentSlot *fakeSlot - nextSlot *fakeSlot - keyToRevoke string - noTaintedKeys bool - - expectKeyToTaint crypto.PublicKey - expectLogs []spiretest.LogEntry - expectCode codes.Code - expectMsg string - expectResp *localauthorityv1.RevokeX509AuthorityResponse + name string + currentSlot *fakeSlot + nextSlot *fakeSlot + keyToRevoke string + noTaintedKeys bool + isUpstreamAuthority bool + + expectLogs []spiretest.LogEntry + expectCode codes.Code + expectMsg string + expectResp *localauthorityv1.RevokeX509AuthorityResponse }{ { name: "revoke authority from parameter", currentSlot: createSlot(journal.Status_ACTIVE, currentAuthorityID, currentKey.Public(), notAfterCurrent), nextSlot: createSlot(journal.Status_OLD, nextAuthorityID, nextKey.Public(), notAfterNext), - keyToRevoke: oldAuthorityID, + keyToRevoke: nextAuthorityID, expectResp: &localauthorityv1.RevokeX509AuthorityResponse{ RevokedAuthority: &localauthorityv1.AuthorityState{ - AuthorityId: oldAuthorityID, + AuthorityId: nextAuthorityID, }, }, expectLogs: []spiretest.LogEntry{ @@ -1586,14 +1898,14 @@ func TestRevokeX509Authority(t *testing.T) { Data: logrus.Fields{ telemetry.Status: "success", telemetry.Type: "audit", - telemetry.LocalAuthorityID: oldAuthorityID, + telemetry.LocalAuthorityID: nextAuthorityID, }, }, { Level: logrus.InfoLevel, Message: "X.509 authority revoked successfully", Data: logrus.Fields{ - telemetry.LocalAuthorityID: oldAuthorityID, + telemetry.LocalAuthorityID: nextAuthorityID, }, }, }, @@ -1630,13 +1942,13 @@ func TestRevokeX509Authority(t *testing.T) { nextSlot: createSlot(journal.Status_PREPARED, nextAuthorityID, nextKey.Public(), notAfterNext), keyToRevoke: nextAuthorityID, expectCode: codes.InvalidArgument, - expectMsg: "invalid authority ID: unable to use a prepared key", + expectMsg: "invalid authority ID: only Old local authority can be revoked", expectLogs: []spiretest.LogEntry{ { Level: logrus.ErrorLevel, Message: "Invalid argument: invalid authority ID", Data: logrus.Fields{ - logrus.ErrorKey: "unable to use a prepared key", + logrus.ErrorKey: "only Old local authority can be revoked", telemetry.LocalAuthorityID: nextAuthorityID, }, }, @@ -1646,7 +1958,7 @@ func TestRevokeX509Authority(t *testing.T) { Data: logrus.Fields{ telemetry.Status: "error", telemetry.StatusCode: "InvalidArgument", - telemetry.StatusMessage: "invalid authority ID: unable to use a prepared key", + telemetry.StatusMessage: "invalid authority ID: only Old local authority can be revoked", telemetry.Type: "audit", telemetry.LocalAuthorityID: nextAuthorityID, }, @@ -1685,17 +1997,17 @@ func TestRevokeX509Authority(t *testing.T) { { name: "ds fails to revoke", currentSlot: createSlot(journal.Status_ACTIVE, currentAuthorityID, currentKey.Public(), notAfterCurrent), - nextSlot: createSlot(journal.Status_OLD, nextAuthorityID, nextKey.Public(), notAfterNext), - keyToRevoke: authorityIDKeyA, - expectCode: codes.InvalidArgument, - expectMsg: "invalid authority ID: no ca found with provided authority ID", + nextSlot: createSlot(journal.Status_OLD, noStoredAuthorityID, noStoredKey.Public(), notAfterNext), + keyToRevoke: noStoredAuthorityID, + expectCode: codes.Internal, + expectMsg: "failed to revoke X.509 authority: no root CA found with provided subject key ID", expectLogs: []spiretest.LogEntry{ { Level: logrus.ErrorLevel, - Message: "Invalid argument: invalid authority ID", + Message: "Failed to revoke X.509 authority", Data: logrus.Fields{ - logrus.ErrorKey: "no ca found with provided authority ID", - telemetry.LocalAuthorityID: authorityIDKeyA, + logrus.ErrorKey: "rpc error: code = NotFound desc = no root CA found with provided subject key ID", + telemetry.LocalAuthorityID: noStoredAuthorityID, }, }, { @@ -1703,10 +2015,10 @@ func TestRevokeX509Authority(t *testing.T) { Message: "API accessed", Data: logrus.Fields{ telemetry.Status: "error", - telemetry.StatusCode: "InvalidArgument", - telemetry.StatusMessage: "invalid authority ID: no ca found with provided authority ID", + telemetry.StatusCode: "Internal", + telemetry.StatusMessage: "failed to revoke X.509 authority: no root CA found with provided subject key ID", telemetry.Type: "audit", - telemetry.LocalAuthorityID: authorityIDKeyA, + telemetry.LocalAuthorityID: noStoredAuthorityID, }, }, }, @@ -1741,6 +2053,35 @@ func TestRevokeX509Authority(t *testing.T) { }, }, }, + { + name: "unable to revoke upstream authority", + currentSlot: createSlot(journal.Status_ACTIVE, currentAuthorityID, currentKey.Public(), notAfterCurrent), + nextSlot: createSlot(journal.Status_OLD, nextAuthorityID, nextKey.Public(), notAfterNext), + keyToRevoke: nextAuthorityID, + isUpstreamAuthority: true, + expectCode: codes.FailedPrecondition, + expectMsg: "local authority can't be revoked if there is an upstream authority", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Local authority can't be revoked if there is an upstream authority", + Data: logrus.Fields{ + telemetry.LocalAuthorityID: nextAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "FailedPrecondition", + telemetry.StatusMessage: "local authority can't be revoked if there is an upstream authority", + telemetry.Type: "audit", + telemetry.LocalAuthorityID: nextAuthorityID, + }, + }, + }, + }, } { t.Run(tt.name, func(t *testing.T) { test := setupServiceTest(t) @@ -1748,14 +2089,7 @@ func TestRevokeX509Authority(t *testing.T) { test.ca.currentX509CASlot = tt.currentSlot test.ca.nextX509CASlot = tt.nextSlot - - var taintedKeys []*common.X509TaintedKey - if !tt.noTaintedKeys { - taintedKeys = []*common.X509TaintedKey{ - {PublicKey: nextPublicKeyRaw}, - {PublicKey: oldPublicKeyRaw}, - } - } + test.ca.isUpstreamAuthority = tt.isUpstreamAuthority _, err := test.ds.CreateBundle(ctx, &common.Bundle{ TrustDomainId: serverTrustDomain.IDString(), @@ -1764,13 +2098,10 @@ func TestRevokeX509Authority(t *testing.T) { DerBytes: currentCA.Raw, }, { - DerBytes: nextCA.Raw, - }, - { - DerBytes: oldCA.Raw, + DerBytes: nextCA.Raw, + TaintedKey: !tt.noTaintedKeys, }, }, - X509TaintedKeys: taintedKeys, }) require.NoError(t, err) @@ -1785,6 +2116,272 @@ func TestRevokeX509Authority(t *testing.T) { } } +func TestRevokeX509UpstreamAuthority(t *testing.T) { + getUpstreamCertAndSubjectID := func(ca *testca.CA) (*x509.Certificate, string) { + // Self signed CA will return itself + cert := ca.X509Authorities()[0] + return cert, x509util.SubjectKeyIDToString(cert.SubjectKeyId) + } + + // Create active upstream authority + activeUpstreamAuthority := testca.New(t, serverTrustDomain) + activeUpstreamAuthorityCert, activeUpstreamAuthorityID := getUpstreamCertAndSubjectID(activeUpstreamAuthority) + + // Create newUpstreamAuthority childs + currentIntermediateCA := activeUpstreamAuthority.ChildCA(testca.WithID(serverTrustDomain.ID())) + nextIntermediateCA := activeUpstreamAuthority.ChildCA(testca.WithID(serverTrustDomain.ID())) + + // Create old upstream authority + deactivatedUpstreamAuthority := testca.New(t, serverTrustDomain) + deactivatedUpstreamAuthorityCert, deactivatedUpstreamAuthorityID := getUpstreamCertAndSubjectID(deactivatedUpstreamAuthority) + + // Create intermediate using old upstream authority + oldIntermediateCA := deactivatedUpstreamAuthority.ChildCA(testca.WithID(serverTrustDomain.ID())) + + for _, tt := range []struct { + name string + currentSlot *fakeSlot + nextSlot *fakeSlot + subjectKeyIDToRevoke string + noTaintedKeys bool + isLocalAuthority bool + + expectLogs []spiretest.LogEntry + expectCode codes.Code + expectMsg string + expectResp *localauthorityv1.RevokeX509UpstreamAuthorityResponse + }{ + { + name: "revoke authority", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, oldIntermediateCA, notAfterNext), + subjectKeyIDToRevoke: deactivatedUpstreamAuthorityID, + expectResp: &localauthorityv1.RevokeX509UpstreamAuthorityResponse{}, + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "success", + telemetry.Type: "audit", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "X.509 upstream authority successfully revoked", + Data: logrus.Fields{ + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + }, + }, + { + name: "unable to revoke with upstream disabled", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, oldIntermediateCA, notAfterNext), + subjectKeyIDToRevoke: deactivatedUpstreamAuthorityID, + expectCode: codes.FailedPrecondition, + expectMsg: "upstream authority is not configured", + isLocalAuthority: true, + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Upstream authority is not configured", + Data: logrus.Fields{ + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "FailedPrecondition", + telemetry.StatusMessage: "upstream authority is not configured", + telemetry.Type: "audit", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + }, + }, + { + name: "no subjectID provided", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, oldIntermediateCA, notAfterNext), + expectCode: codes.InvalidArgument, + expectMsg: "invalid subject key ID: no subject key ID provided", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Invalid argument: invalid subject key ID", + Data: logrus.Fields{ + logrus.ErrorKey: "no subject key ID provided", + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "InvalidArgument", + telemetry.StatusMessage: "invalid subject key ID: no subject key ID provided", + telemetry.Type: "audit", + }, + }, + }, + }, + { + name: "unable to use active upstream authority", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, nextIntermediateCA, notAfterNext), + subjectKeyIDToRevoke: activeUpstreamAuthorityID, + expectCode: codes.InvalidArgument, + expectMsg: "invalid subject key ID: unable to use upstream authority singing current authority", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Invalid argument: invalid subject key ID", + Data: logrus.Fields{ + logrus.ErrorKey: "unable to use upstream authority singing current authority", + telemetry.SubjectKeyID: activeUpstreamAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "InvalidArgument", + telemetry.StatusMessage: "invalid subject key ID: unable to use upstream authority singing current authority", + telemetry.Type: "audit", + telemetry.SubjectKeyID: activeUpstreamAuthorityID, + }, + }, + }, + }, + { + name: "unknown subjectKeyID", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, nextIntermediateCA, notAfterNext), + subjectKeyIDToRevoke: "invalidID", + expectCode: codes.InvalidArgument, + expectMsg: "invalid subject key ID: upstream authority didn't sign the old local authority", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Invalid argument: invalid subject key ID", + Data: logrus.Fields{ + logrus.ErrorKey: "upstream authority didn't sign the old local authority", + telemetry.SubjectKeyID: "invalidID", + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "InvalidArgument", + telemetry.StatusMessage: "invalid subject key ID: upstream authority didn't sign the old local authority", + telemetry.Type: "audit", + telemetry.SubjectKeyID: "invalidID", + }, + }, + }, + }, + { + name: "prepared authority signed by upstream authority", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_PREPARED, oldIntermediateCA, notAfterNext), + subjectKeyIDToRevoke: deactivatedUpstreamAuthorityID, + expectCode: codes.InvalidArgument, + expectMsg: "invalid subject key ID: only upstream authorities signing an old authority can be used", + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Invalid argument: invalid subject key ID", + Data: logrus.Fields{ + logrus.ErrorKey: "only upstream authorities signing an old authority can be used", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "InvalidArgument", + telemetry.StatusMessage: "invalid subject key ID: only upstream authorities signing an old authority can be used", + telemetry.Type: "audit", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + }, + }, + { + name: "ds failed revoke untainted keys", + currentSlot: createSlotWithUpstream(journal.Status_ACTIVE, currentIntermediateCA, notAfterCurrent), + nextSlot: createSlotWithUpstream(journal.Status_OLD, oldIntermediateCA, notAfterNext), + subjectKeyIDToRevoke: deactivatedUpstreamAuthorityID, + expectCode: codes.Internal, + expectMsg: "failed to revoke X.509 upstream authority: it is not possible to revoke an untainted root CA", + noTaintedKeys: true, + expectLogs: []spiretest.LogEntry{ + { + Level: logrus.ErrorLevel, + Message: "Failed to revoke X.509 upstream authority", + Data: logrus.Fields{ + logrus.ErrorKey: "rpc error: code = InvalidArgument desc = it is not possible to revoke an untainted root CA", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + { + Level: logrus.InfoLevel, + Message: "API accessed", + Data: logrus.Fields{ + telemetry.Status: "error", + telemetry.StatusCode: "Internal", + telemetry.StatusMessage: "failed to revoke X.509 upstream authority: it is not possible to revoke an untainted root CA", + telemetry.Type: "audit", + telemetry.SubjectKeyID: deactivatedUpstreamAuthorityID, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + test := setupServiceTest(t) + defer test.Cleanup() + + test.ca.currentX509CASlot = tt.currentSlot + test.ca.nextX509CASlot = tt.nextSlot + test.ca.isUpstreamAuthority = !tt.isLocalAuthority + + _, err := test.ds.CreateBundle(ctx, &common.Bundle{ + TrustDomainId: serverTrustDomain.IDString(), + RootCas: []*common.Certificate{ + { + DerBytes: activeUpstreamAuthorityCert.Raw, + }, + { + DerBytes: deactivatedUpstreamAuthorityCert.Raw, + TaintedKey: !tt.noTaintedKeys, + }, + }, + }) + require.NoError(t, err) + + resp, err := test.client.RevokeX509UpstreamAuthority(ctx, &localauthorityv1.RevokeX509UpstreamAuthorityRequest{ + SubjectKeyId: tt.subjectKeyIDToRevoke, + }) + + spiretest.AssertGRPCStatusHasPrefix(t, err, tt.expectCode, tt.expectMsg) + spiretest.AssertProtoEqual(t, tt.expectResp, resp) + spiretest.AssertLogs(t, test.logHook.AllEntries(), tt.expectLogs) + }) + } +} + func setupServiceTest(t *testing.T) *serviceTest { ds := fakedatastore.New(t) m := &fakeCAManager{} @@ -1846,7 +2443,12 @@ type fakeCAManager struct { prepareJWTKeyErr error - prepareX509CAErr error + prepareX509CAErr error + isUpstreamAuthority bool +} + +func (m *fakeCAManager) IsUpstreamAuthority() bool { + return m.isUpstreamAuthority } func (m *fakeCAManager) GetCurrentJWTKeySlot() manager.Slot { @@ -1884,10 +2486,15 @@ func (m *fakeCAManager) RotateX509CA(context.Context) { type fakeSlot struct { manager.Slot - authorityID string - notAfter time.Time - publicKey crypto.PublicKey - status journal.Status + authorityID string + upstreamAuthorityID string + notAfter time.Time + publicKey crypto.PublicKey + status journal.Status +} + +func (s *fakeSlot) UpstreamAuthorityID() string { + return s.upstreamAuthorityID } func (s *fakeSlot) AuthorityID() string { @@ -1914,3 +2521,12 @@ func createSlot(status journal.Status, authorityID string, publicKey crypto.Publ status: status, } } + +func createSlotWithUpstream(status journal.Status, ca *testca.CA, notAfter time.Time) *fakeSlot { + return &fakeSlot{ + authorityID: ca.GetSubjectKeyID(), + notAfter: notAfter, + status: status, + upstreamAuthorityID: ca.GetUpstreamAuthorityID(), + } +} diff --git a/pkg/server/bundle/datastore/wrapper.go b/pkg/server/bundle/datastore/wrapper.go index fc56152de8..7ef9dc7145 100644 --- a/pkg/server/bundle/datastore/wrapper.go +++ b/pkg/server/bundle/datastore/wrapper.go @@ -2,7 +2,6 @@ package datastore import ( "context" - "crypto" "time" "github.com/spiffe/spire/pkg/server/datastore" @@ -39,8 +38,8 @@ func (w datastoreWrapper) PruneBundle(ctx context.Context, trustDomainID string, return changed, err } -func (w datastoreWrapper) RevokeX509CA(ctx context.Context, trustDomainID string, publicKeyToRevoke crypto.PublicKey) error { - err := w.DataStore.RevokeX509CA(ctx, trustDomainID, publicKeyToRevoke) +func (w datastoreWrapper) RevokeX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToRevoke string) error { + err := w.DataStore.RevokeX509CA(ctx, trustDomainID, subjectKeyIDToRevoke) if err == nil { w.bundleUpdated() } diff --git a/pkg/server/bundle/datastore/wrapper_test.go b/pkg/server/bundle/datastore/wrapper_test.go index 93aed471a5..6fe3a42e3d 100644 --- a/pkg/server/bundle/datastore/wrapper_test.go +++ b/pkg/server/bundle/datastore/wrapper_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/spire/pkg/common/x509util" "github.com/spiffe/spire/pkg/server/datastore" "github.com/spiffe/spire/proto/spire/common" "github.com/spiffe/spire/test/fakes/fakedatastore" @@ -52,12 +53,13 @@ func TestWithBundlePublisher(t *testing.T) { { name: "RevokeX509CA", assertCallingCallback: func(ctx context.Context, t *testing.T, ds datastore.DataStore, wt *wrapperTest) { - require.NoError(t, ds.TaintX509CA(ctx, bundle2.TrustDomainId, rootCA.PublicKey)) + subjectKeyID := x509util.SubjectKeyIDToString(rootCA.SubjectKeyId) + require.NoError(t, ds.TaintX509CA(ctx, bundle2.TrustDomainId, subjectKeyID)) // TaintX509CA should not call the callback function require.False(t, wt.callbackCalled) - require.NoError(t, ds.RevokeX509CA(ctx, bundle2.TrustDomainId, rootCA.PublicKey)) + require.NoError(t, ds.RevokeX509CA(ctx, bundle2.TrustDomainId, subjectKeyID)) require.True(t, wt.callbackCalled) }, }, diff --git a/pkg/server/ca/manager/journal.go b/pkg/server/ca/manager/journal.go index 14463fba5c..ae9250caf1 100644 --- a/pkg/server/ca/manager/journal.go +++ b/pkg/server/ca/manager/journal.go @@ -61,13 +61,14 @@ func (j *Journal) AppendX509CA(ctx context.Context, slotID string, issuedAt time backup := j.entries.X509CAs j.entries.X509CAs = append(j.entries.X509CAs, &journal.X509CAEntry{ - SlotId: slotID, - IssuedAt: issuedAt.Unix(), - NotAfter: x509CA.Certificate.NotAfter.Unix(), - Certificate: x509CA.Certificate.Raw, - UpstreamChain: chainDER(x509CA.UpstreamChain), - Status: journal.Status_PREPARED, - AuthorityId: x509util.SubjectKeyIDToString(x509CA.Certificate.SubjectKeyId), + SlotId: slotID, + IssuedAt: issuedAt.Unix(), + NotAfter: x509CA.Certificate.NotAfter.Unix(), + Certificate: x509CA.Certificate.Raw, + UpstreamChain: chainDER(x509CA.UpstreamChain), + Status: journal.Status_PREPARED, + AuthorityId: x509util.SubjectKeyIDToString(x509CA.Certificate.SubjectKeyId), + UpstreamAuthorityId: x509util.SubjectKeyIDToString(x509CA.Certificate.AuthorityKeyId), }) exceeded := len(j.entries.X509CAs) - journalCap diff --git a/pkg/server/ca/manager/manager.go b/pkg/server/ca/manager/manager.go index 9477ce2ea1..0ecf39be8e 100644 --- a/pkg/server/ca/manager/manager.go +++ b/pkg/server/ca/manager/manager.go @@ -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" @@ -227,6 +228,7 @@ func (m *Manager) PrepareX509CA(ctx context.Context) (err error) { // Set key from new CA, to be able to get it after // slot moved to old state slot.authorityID = x509util.SubjectKeyIDToString(x509CA.Certificate.SubjectKeyId) + slot.upstreamAuthorityID = x509util.SubjectKeyIDToString(x509CA.Certificate.AuthorityKeyId) slot.publicKey = slot.x509CA.Certificate.PublicKey slot.notAfter = slot.x509CA.Certificate.NotAfter @@ -235,15 +237,20 @@ func (m *Manager) PrepareX509CA(ctx context.Context) (err error) { } m.c.Log.WithFields(logrus.Fields{ - telemetry.Slot: slot.id, - telemetry.IssuedAt: slot.issuedAt, - telemetry.Expiration: slot.x509CA.Certificate.NotAfter, - telemetry.SelfSigned: m.upstreamClient == nil, - telemetry.LocalAuthorityID: slot.authorityID, + telemetry.Slot: slot.id, + telemetry.IssuedAt: slot.issuedAt, + telemetry.Expiration: slot.x509CA.Certificate.NotAfter, + telemetry.SelfSigned: m.upstreamClient == nil, + telemetry.LocalAuthorityID: slot.authorityID, + telemetry.UpstreamAuthorityID: slot.upstreamAuthorityID, }).Info("X509 CA prepared") 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() @@ -493,10 +500,11 @@ func (m *Manager) activateJWTKey(ctx context.Context) { func (m *Manager) activateX509CA(ctx context.Context) { log := m.c.Log.WithFields(logrus.Fields{ - telemetry.Slot: m.currentX509CA.id, - telemetry.IssuedAt: m.currentX509CA.issuedAt, - telemetry.Expiration: m.currentX509CA.x509CA.Certificate.NotAfter, - telemetry.LocalAuthorityID: m.currentX509CA.authorityID, + telemetry.Slot: m.currentX509CA.id, + telemetry.IssuedAt: m.currentX509CA.issuedAt, + telemetry.Expiration: m.currentX509CA.x509CA.Certificate.NotAfter, + telemetry.LocalAuthorityID: m.currentX509CA.authorityID, + telemetry.UpstreamAuthorityID: m.currentX509CA.upstreamAuthorityID, }) log.Info("X509 CA activated") telemetry_server.IncrActivateX509CAManagerCounter(m.c.Metrics) @@ -725,17 +733,56 @@ type bundleUpdater struct { updated func() } -func (u *bundleUpdater) AppendX509Roots(ctx context.Context, roots []*x509.Certificate) error { +func (u *bundleUpdater) SyncX509Roots(ctx context.Context, roots []*x509certificate.X509Authority) error { bundle := &common.Bundle{ TrustDomainId: u.trustDomainID, RootCas: make([]*common.Certificate, 0, len(roots)), } + x509Authorities, err := u.fetchX509Authorities(ctx) + if err != nil { + return err + } + + newAuthorities := make(map[string]struct{}, len(roots)) for _, root := range roots { + skID := x509util.SubjectKeyIDToString(root.Certificate.SubjectKeyId) + // Collect all skIDs + newAuthorities[skID] = struct{}{} + + // Verify if new root ca is tainted + if root.Tainted { + // Taint x.509 authority, if required + if found, ok := x509Authorities[skID]; ok && !found.Tainted { + if err := u.ds.TaintX509CA(ctx, u.trustDomainID, skID); err != nil { + return fmt.Errorf("failed to taint x.509 authority %q: %w", skID, err) + } + u.log.WithField(telemetry.SubjectKeyID, skID).Info("X.509 authority tainted") + // Prevent to add tainted keys, since status is updated before + continue + } + } + bundle.RootCas = append(bundle.RootCas, &common.Certificate{ - DerBytes: root.Raw, + DerBytes: root.Certificate.Raw, + TaintedKey: root.Tainted, }) } + + for skID, authority := range x509Authorities { + // Only tainted keys can ke revoked + if authority.Tainted { + // In case a stored tainted authority is not found, + // from latest bundle update, then revoke it + if _, found := newAuthorities[skID]; !found { + if err := u.ds.RevokeX509CA(ctx, u.trustDomainID, skID); err != nil { + return fmt.Errorf("failed to revoke a tainted key %q: %w", skID, err) + } + u.log.WithField(telemetry.SubjectKeyID, skID).Info("X.509 authority revoked") + } + } + } + if _, err := u.appendBundle(ctx, bundle); err != nil { return err } @@ -757,6 +804,32 @@ func (u *bundleUpdater) LogError(err error, msg string) { u.log.WithError(err).Error(msg) } +func (u *bundleUpdater) fetchX509Authorities(ctx context.Context) (map[string]*x509certificate.X509Authority, error) { + bundle, err := u.ds.FetchBundle(ctx, u.trustDomainID) + if err != nil { + return nil, fmt.Errorf("failed to fetch bundle: %w", err) + } + // Bundle not found + if bundle == nil { + return nil, nil + } + + authorities := map[string]*x509certificate.X509Authority{} + for _, eachRoot := range bundle.RootCas { + cert, err := x509.ParseCertificate(eachRoot.DerBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse root certificate: %w", err) + } + + authorities[x509util.SubjectKeyIDToString(cert.SubjectKeyId)] = &x509certificate.X509Authority{ + Certificate: cert, + Tainted: eachRoot.TaintedKey, + } + } + + return authorities, nil +} + func (u *bundleUpdater) appendBundle(ctx context.Context, bundle *common.Bundle) (*common.Bundle, error) { dsBundle, err := u.ds.AppendBundle(ctx, bundle) if err != nil { diff --git a/pkg/server/ca/manager/manager_test.go b/pkg/server/ca/manager/manager_test.go index 3049851b70..cc249d147d 100644 --- a/pkg/server/ca/manager/manager_test.go +++ b/pkg/server/ca/manager/manager_test.go @@ -17,7 +17,9 @@ import ( "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" telemetry_server "github.com/spiffe/spire/pkg/common/telemetry/server" + "github.com/spiffe/spire/pkg/common/x509util" "github.com/spiffe/spire/pkg/server/ca" "github.com/spiffe/spire/pkg/server/credtemplate" "github.com/spiffe/spire/pkg/server/credvalidator" @@ -56,6 +58,7 @@ func TestGetCurrentJWTKeySlot(t *testing.T) { test := setupTest(t) test.initSelfSignedManager() + require.False(t, test.m.IsUpstreamAuthority()) t.Run("no authority created", func(t *testing.T) { currentSlot := test.m.GetCurrentJWTKeySlot() @@ -126,6 +129,7 @@ func TestGetCurrentX509CASlot(t *testing.T) { slot := currentSlot.(*x509CASlot) require.Nil(t, slot.x509CA) require.Empty(t, slot.authorityID) + require.Empty(t, slot.upstreamAuthorityID) require.Empty(t, slot.issuedAt) require.Empty(t, slot.publicKey) require.Empty(t, slot.notAfter) @@ -141,6 +145,7 @@ func TestGetCurrentX509CASlot(t *testing.T) { slot := currentSlot.(*x509CASlot) require.NotNil(t, slot.x509CA) require.NotEmpty(t, slot.authorityID) + require.Empty(t, slot.upstreamAuthorityID) require.NotNil(t, slot.publicKey) require.Equal(t, expectIssuedAt, slot.issuedAt) require.Equal(t, expectNotAfter, slot.notAfter) @@ -159,6 +164,7 @@ func TestGetNextX509CASlot(t *testing.T) { require.Nil(t, slot.x509CA) require.Empty(t, slot.authorityID) + require.Empty(t, slot.upstreamAuthorityID) require.Empty(t, slot.issuedAt) require.Empty(t, slot.publicKey) require.Empty(t, slot.notAfter) @@ -174,6 +180,7 @@ func TestGetNextX509CASlot(t *testing.T) { slot := nextSlot.(*x509CASlot) require.NotNil(t, slot.x509CA) require.NotEmpty(t, slot.authorityID) + require.Empty(t, slot.upstreamAuthorityID) require.NotNil(t, slot.publicKey) require.Equal(t, expectIssuedAt, slot.issuedAt) require.Equal(t, expectNotAfter, slot.notAfter) @@ -268,13 +275,14 @@ func TestUpstreamSigned(t *testing.T) { }) test.initAndActivateUpstreamSignedManager(ctx, upstreamAuthority) + require.True(t, test.m.IsUpstreamAuthority()) // X509 CA should be set up to be an intermediate but only have itself // in the chain since it was signed directly by the upstream root. x509CA := test.currentX509CA() assert.NotNil(t, x509CA.Signer) if assert.NotNil(t, x509CA.Certificate) { - assert.Equal(t, fakeUA.X509Root().Subject, x509CA.Certificate.Issuer) + assert.Equal(t, fakeUA.X509Root().Certificate.Subject, x509CA.Certificate.Issuer) } if assert.Len(t, x509CA.UpstreamChain, 1) { assert.Equal(t, x509CA.Certificate, x509CA.UpstreamChain[0]) @@ -290,6 +298,76 @@ func TestUpstreamSigned(t *testing.T) { "by this server may have trouble communicating with workloads outside "+ "this cluster when using JWT-SVIDs."), ) + + // Taint first root + err := fakeUA.TaintAuthority(0) + require.NoError(t, err) + + // Get the roots again and verify that the first X.509 authority is tainted + x509Roots := fakeUA.X509Roots() + require.True(t, x509Roots[0].Tainted) + + commonCertificates := x509certificate.RequireToCommonProtos(x509Roots) + // Retry until the Tainted attribute is propagated to the database + require.Eventually(t, func() bool { + bundle := test.fetchBundle(ctx) + return spiretest.AssertProtoListEqual(t, commonCertificates, bundle.RootCas) + }, time.Minute, 500*time.Millisecond) +} + +func TestGetCurrentX509CASlotUpstreamSigned(t *testing.T) { + ctx := context.Background() + + test := setupTest(t) + + upstreamAuthority, ua := fakeupstreamauthority.Load(t, fakeupstreamauthority.Config{ + TrustDomain: testTrustDomain, + DisallowPublishJWTKey: true, + }) + + test.initAndActivateUpstreamSignedManager(ctx, upstreamAuthority) + + expectIssuedAt := test.clock.Now() + expectNotAfter := expectIssuedAt.Add(test.m.caTTL).UTC() + expectUpstreamAuthorityID := x509util.SubjectKeyIDToString(ua.X509Root().Certificate.SubjectKeyId) + + require.NoError(t, test.m.PrepareX509CA(ctx)) + + currentSlot := test.m.GetCurrentX509CASlot() + slot := currentSlot.(*x509CASlot) + require.NotNil(t, slot.x509CA) + require.NotEmpty(t, slot.authorityID) + require.Equal(t, expectUpstreamAuthorityID, slot.upstreamAuthorityID) + require.NotNil(t, slot.publicKey) + require.Equal(t, expectIssuedAt, slot.issuedAt) + require.Equal(t, expectNotAfter, slot.notAfter) +} + +func TestGetNextX509CASlotUpstreamSigned(t *testing.T) { + ctx := context.Background() + + test := setupTest(t) + upstreamAuthority, ua := fakeupstreamauthority.Load(t, fakeupstreamauthority.Config{ + TrustDomain: testTrustDomain, + DisallowPublishJWTKey: true, + }) + + test.initAndActivateUpstreamSignedManager(ctx, upstreamAuthority) + + expectIssuedAt := test.clock.Now() + expectNotAfter := expectIssuedAt.Add(test.m.caTTL).UTC() + expectUpstreamAuthorityID := x509util.SubjectKeyIDToString(ua.X509Root().Certificate.SubjectKeyId) + + require.NoError(t, test.m.PrepareX509CA(ctx)) + + nextSlot := test.m.GetNextX509CASlot() + slot := nextSlot.(*x509CASlot) + require.NotNil(t, slot.x509CA) + require.NotEmpty(t, slot.authorityID) + require.Equal(t, expectUpstreamAuthorityID, slot.upstreamAuthorityID) + require.NotNil(t, slot.publicKey) + require.Equal(t, expectIssuedAt, slot.issuedAt) + require.Equal(t, expectNotAfter, slot.notAfter) } func TestUpstreamSignedProducesInvalidChain(t *testing.T) { @@ -399,7 +477,7 @@ func TestX509CARotation(t *testing.T) { require.Equal(t, journal.Status_ACTIVE, test.currentX509CAStatus()) assert.Nil(t, test.nextX509CA(), "second X509CA should not be prepared yet") require.Equal(t, journal.Status_UNKNOWN, test.nextX509CAStatus()) - test.requireBundleRootCAs(ctx, t, first.Certificate) + test.requireIntermediateRootCA(ctx, t, first.Certificate) // Prepare new X509CA. the current X509CA should stay // the same but the next X509CA should have been prepared and added to @@ -411,7 +489,7 @@ func TestX509CARotation(t *testing.T) { second := test.nextX509CA() assert.NotNil(t, second, "second X509CA should have been prepared") require.Equal(t, journal.Status_PREPARED, test.nextX509CAStatus()) - test.requireBundleRootCAs(ctx, t, first.Certificate, second.Certificate) + test.requireIntermediateRootCA(ctx, t, first.Certificate, second.Certificate) // we should now have a bundle update notification due to the preparation test.waitForBundleUpdatedNotification(ctx, notifyCh) @@ -433,7 +511,7 @@ func TestX509CARotation(t *testing.T) { third := test.nextX509CA() assert.NotNil(t, third, "third X509CA should have been prepared") require.Equal(t, journal.Status_PREPARED, test.nextX509CAStatus()) - test.requireBundleRootCAs(ctx, t, first.Certificate, second.Certificate, third.Certificate) + test.requireIntermediateRootCA(ctx, t, first.Certificate, second.Certificate, third.Certificate) // we should now have another bundle update notification due to the preparation test.waitForBundleUpdatedNotification(ctx, notifyCh) @@ -562,7 +640,7 @@ func TestPruneBundle(t *testing.T) { firstJWTKey := test.currentJWTKey() secondX509CA := test.nextX509CA() secondJWTKey := test.nextJWTKey() - test.requireBundleRootCAs(ctx, t, firstX509CA.Certificate, secondX509CA.Certificate) + test.requireIntermediateRootCA(ctx, t, firstX509CA.Certificate, secondX509CA.Certificate) test.requireBundleJWTKeys(ctx, t, firstJWTKey, secondJWTKey) // kick off a goroutine to service bundle update notifications. This is @@ -573,13 +651,13 @@ func TestPruneBundle(t *testing.T) { // advance just past the expiration time of the first and prune. nothing // should change. test.setTimeAndPrune(firstExpiresTime.Add(time.Minute)) - test.requireBundleRootCAs(ctx, t, firstX509CA.Certificate, secondX509CA.Certificate) + test.requireIntermediateRootCA(ctx, t, firstX509CA.Certificate, secondX509CA.Certificate) test.requireBundleJWTKeys(ctx, t, firstJWTKey, secondJWTKey) // advance beyond the safety threshold of the first, prune, and assert that // the first has been pruned test.addTimeAndPrune(safetyThresholdBundle) - test.requireBundleRootCAs(ctx, t, secondX509CA.Certificate) + test.requireIntermediateRootCA(ctx, t, secondX509CA.Certificate) test.requireBundleJWTKeys(ctx, t, secondJWTKey) // we should now have a bundle update notification due to the pruning @@ -589,7 +667,7 @@ func TestPruneBundle(t *testing.T) { // changes because we can't prune out the whole bundle. test.clock.Set(secondExpiresTime.Add(time.Minute + safetyThresholdBundle)) require.EqualError(t, test.m.PruneBundle(context.Background()), "unable to prune bundle: rpc error: code = Unknown desc = prune failed: would prune all certificates") - test.requireBundleRootCAs(ctx, t, secondX509CA.Certificate) + test.requireIntermediateRootCA(ctx, t, secondX509CA.Certificate) test.requireBundleJWTKeys(ctx, t, secondJWTKey) } @@ -1110,7 +1188,7 @@ func (m *managerTest) getSignerInfo(signer crypto.Signer) signerInfo { } } -func (m *managerTest) requireBundleRootCAs(ctx context.Context, t *testing.T, rootCAs ...*x509.Certificate) { +func (m *managerTest) requireIntermediateRootCA(ctx context.Context, t *testing.T, rootCAs ...*x509.Certificate) { expected := &common.Bundle{} for _, rootCA := range rootCAs { expected.RootCas = append(expected.RootCas, &common.Certificate{ @@ -1124,6 +1202,21 @@ func (m *managerTest) requireBundleRootCAs(ctx context.Context, t *testing.T, ro }) } +func (m *managerTest) requireBundleRootCAs(ctx context.Context, t *testing.T, rootCAs ...*x509certificate.X509Authority) { + expected := &common.Bundle{} + for _, rootCA := range rootCAs { + expected.RootCas = append(expected.RootCas, &common.Certificate{ + DerBytes: rootCA.Certificate.Raw, + TaintedKey: rootCA.Tainted, + }) + } + + bundle := m.fetchBundle(ctx) + spiretest.RequireProtoEqual(t, expected, &common.Bundle{ + RootCas: bundle.RootCas, + }) +} + func (m *managerTest) requireBundleJWTKeys(ctx context.Context, t *testing.T, jwtKeys ...*ca.JWTKey) { expected := &common.Bundle{} for _, jwtKey := range jwtKeys { diff --git a/pkg/server/ca/manager/slot.go b/pkg/server/ca/manager/slot.go index a772554631..d47a0c9b39 100644 --- a/pkg/server/ca/manager/slot.go +++ b/pkg/server/ca/manager/slot.go @@ -40,7 +40,9 @@ type Slot interface { ShouldPrepareNext(now time.Time) bool ShouldActivateNext(now time.Time) bool Status() journal.Status + UpstreamAuthorityID() string AuthorityID() string + // TODO: This will be removed as part of #5390 PublicKey() crypto.PublicKey NotAfter() time.Time } @@ -321,19 +323,21 @@ func (s *SlotLoader) tryLoadX509CASlotFromEntry(ctx context.Context, entry *jour slot, badReason, err := s.loadX509CASlotFromEntry(ctx, entry) if err != nil { s.Log.WithError(err).WithFields(logrus.Fields{ - telemetry.Slot: entry.SlotId, - telemetry.IssuedAt: time.Unix(entry.IssuedAt, 0), - telemetry.Status: entry.Status, - telemetry.LocalAuthorityID: entry.AuthorityId, + telemetry.Slot: entry.SlotId, + telemetry.IssuedAt: time.Unix(entry.IssuedAt, 0), + telemetry.Status: entry.Status, + telemetry.LocalAuthorityID: entry.AuthorityId, + telemetry.UpstreamAuthorityID: entry.UpstreamAuthorityId, }).Error("X509CA slot failed to load") return nil, err } if badReason != "" { s.Log.WithError(errors.New(badReason)).WithFields(logrus.Fields{ - telemetry.Slot: entry.SlotId, - telemetry.IssuedAt: time.Unix(entry.IssuedAt, 0), - telemetry.Status: entry.Status, - telemetry.LocalAuthorityID: entry.AuthorityId, + telemetry.Slot: entry.SlotId, + telemetry.IssuedAt: time.Unix(entry.IssuedAt, 0), + telemetry.Status: entry.Status, + telemetry.LocalAuthorityID: entry.AuthorityId, + telemetry.UpstreamAuthorityID: entry.UpstreamAuthorityId, }).Warn("X509CA slot unusable") return nil, nil } @@ -379,10 +383,11 @@ func (s *SlotLoader) loadX509CASlotFromEntry(ctx context.Context, entry *journal Certificate: cert, UpstreamChain: upstreamChain, }, - status: entry.Status, - authorityID: entry.AuthorityId, - publicKey: signer.Public(), - notAfter: cert.NotAfter, + status: entry.Status, + authorityID: entry.AuthorityId, + upstreamAuthorityID: entry.UpstreamAuthorityId, + publicKey: signer.Public(), + notAfter: cert.NotAfter, }, "", nil } @@ -524,13 +529,14 @@ func keyActivationThreshold(issuedAt, notAfter time.Time) time.Time { } type x509CASlot struct { - id string - issuedAt time.Time - x509CA *ca.X509CA - status journal.Status - authorityID string - publicKey crypto.PublicKey - notAfter time.Time + id string + issuedAt time.Time + x509CA *ca.X509CA + status journal.Status + authorityID string + publicKey crypto.PublicKey + notAfter time.Time + upstreamAuthorityID string } func newX509CASlot(id string) *x509CASlot { @@ -539,6 +545,10 @@ func newX509CASlot(id string) *x509CASlot { } } +func (s *x509CASlot) UpstreamAuthorityID() string { + return s.upstreamAuthorityID +} + func (s *x509CASlot) KmKeyID() string { return x509CAKmKeyID(s.id) } @@ -603,6 +613,10 @@ func (s *jwtKeySlot) AuthorityID() string { return s.authorityID } +func (s *jwtKeySlot) UpstreamAuthorityID() string { + return "" +} + func (s *jwtKeySlot) PublicKey() crypto.PublicKey { if s.jwtKey == nil { return nil diff --git a/pkg/server/ca/manager/slot_test.go b/pkg/server/ca/manager/slot_test.go index d7864fecf7..4f8eb784d1 100644 --- a/pkg/server/ca/manager/slot_test.go +++ b/pkg/server/ca/manager/slot_test.go @@ -700,11 +700,12 @@ func TestJournalLoad(t *testing.T) { entries: &journal.Entries{ X509CAs: []*journal.X509CAEntry{ { - SlotId: "A", - IssuedAt: firstIssuedAtUnix, - Certificate: []byte("foo"), - Status: journal.Status_ACTIVE, - AuthorityId: "1", + SlotId: "A", + IssuedAt: firstIssuedAtUnix, + Certificate: []byte("foo"), + Status: journal.Status_ACTIVE, + AuthorityId: "1", + UpstreamAuthorityId: "2", }, }, }, @@ -722,11 +723,12 @@ func TestJournalLoad(t *testing.T) { Level: logrus.ErrorLevel, Message: "X509CA slot failed to load", Data: logrus.Fields{ - logrus.ErrorKey: "unable to parse CA certificate: x509: malformed certificate", - telemetry.IssuedAt: firstIssuedAt.String(), - telemetry.Slot: "A", - telemetry.Status: "ACTIVE", - telemetry.LocalAuthorityID: "1", + logrus.ErrorKey: "unable to parse CA certificate: x509: malformed certificate", + telemetry.IssuedAt: firstIssuedAt.String(), + telemetry.Slot: "A", + telemetry.Status: "ACTIVE", + telemetry.LocalAuthorityID: "1", + telemetry.UpstreamAuthorityID: "2", }, }, }, diff --git a/pkg/server/ca/upstream_client.go b/pkg/server/ca/upstream_client.go index 125f4dfcfb..c4bd64f631 100644 --- a/pkg/server/ca/upstream_client.go +++ b/pkg/server/ca/upstream_client.go @@ -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" @@ -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 + SyncX509Roots(ctx context.Context, roots []*x509certificate.X509Authority) error AppendJWTKeys(ctx context.Context, keys []*common.PublicKey) ([]*common.PublicKey, error) LogError(err error, msg string) } @@ -139,16 +140,22 @@ func (u *UpstreamClient) runMintX509CAStream(ctx context.Context, csr []byte, tt } defer x509RootsStream.Close() + // Extract all root 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 } - if err := u.c.BundleUpdater.AppendX509Roots(ctx, x509Roots); err != nil { + if err := u.c.BundleUpdater.SyncX509Roots(ctx, x509Roots); err != nil { firstResultCh <- mintX509CAResult{err: err} return } @@ -171,7 +178,7 @@ func (u *UpstreamClient) runMintX509CAStream(ctx context.Context, csr []byte, tt return } - if err := u.c.BundleUpdater.AppendX509Roots(ctx, x509Roots); err != nil { + if err := u.c.BundleUpdater.SyncX509Roots(ctx, x509Roots); err != nil { u.c.BundleUpdater.LogError(err, "Failed to store X.509 roots received by the upstream authority plugin.") continue } diff --git a/pkg/server/ca/upstream_client_test.go b/pkg/server/ca/upstream_client_test.go index ee4e75a97a..4921674c57 100644 --- a/pkg/server/ca/upstream_client_test.go +++ b/pkg/server/ca/upstream_client_test.go @@ -12,6 +12,7 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" upstreamauthorityv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/server/upstreamauthority/v1" plugintypes "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/types" + "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" "github.com/spiffe/spire/pkg/server/ca" "github.com/spiffe/spire/pkg/server/credtemplate" "github.com/spiffe/spire/proto/spire/common" @@ -172,20 +173,20 @@ type bundleUpdateErr struct { } type fakeBundleUpdater struct { - x509RootsCh chan []*x509.Certificate + x509RootsCh chan []*x509certificate.X509Authority jwtKeysCh chan []*common.PublicKey errorCh chan bundleUpdateErr } func newFakeBundleUpdater() *fakeBundleUpdater { return &fakeBundleUpdater{ - x509RootsCh: make(chan []*x509.Certificate, 1), + x509RootsCh: make(chan []*x509certificate.X509Authority, 1), jwtKeysCh: make(chan []*common.PublicKey, 1), errorCh: make(chan bundleUpdateErr, 1), } } -func (u *fakeBundleUpdater) AppendX509Roots(ctx context.Context, x509Roots []*x509.Certificate) error { +func (u *fakeBundleUpdater) SyncX509Roots(ctx context.Context, x509Roots []*x509certificate.X509Authority) error { select { case u.x509RootsCh <- x509Roots: return nil @@ -194,7 +195,7 @@ func (u *fakeBundleUpdater) AppendX509Roots(ctx context.Context, x509Roots []*x5 } } -func (u *fakeBundleUpdater) WaitForAppendedX509Roots(t *testing.T) []*x509.Certificate { +func (u *fakeBundleUpdater) WaitForAppendedX509Roots(t *testing.T) []*x509certificate.X509Authority { select { case <-time.After(time.Minute): require.FailNow(t, "timed out waiting for X.509 roots to be appended") diff --git a/pkg/server/cache/dscache/cache.go b/pkg/server/cache/dscache/cache.go index 5f87a499c6..24fd139247 100644 --- a/pkg/server/cache/dscache/cache.go +++ b/pkg/server/cache/dscache/cache.go @@ -2,7 +2,6 @@ package dscache import ( "context" - "crypto" "sync" "time" @@ -104,15 +103,15 @@ 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 } -func (ds *DatastoreCache) RevokeX509CA(ctx context.Context, trustDomainID string, publicKeyToRevoke crypto.PublicKey) (err error) { - if err = ds.DataStore.RevokeX509CA(ctx, trustDomainID, publicKeyToRevoke); err == nil { +func (ds *DatastoreCache) RevokeX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToRevoke string) (err error) { + if err = ds.DataStore.RevokeX509CA(ctx, trustDomainID, subjectKeyIDToRevoke); err == nil { ds.invalidateBundleEntry(trustDomainID) } return diff --git a/pkg/server/datastore/datastore.go b/pkg/server/datastore/datastore.go index 8b6f2aa0ef..6cc3cfca5a 100644 --- a/pkg/server/datastore/datastore.go +++ b/pkg/server/datastore/datastore.go @@ -2,7 +2,6 @@ package datastore import ( "context" - "crypto" "net/url" "time" @@ -25,8 +24,8 @@ type DataStore interface { UpdateBundle(context.Context, *common.Bundle, *common.BundleMask) (*common.Bundle, error) // Keys - TaintX509CA(ctx context.Context, trustDomainID string, publicKeyToTaint crypto.PublicKey) error - RevokeX509CA(ctx context.Context, trustDomainID string, publicKeyToRevoke crypto.PublicKey) error + TaintX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToTaint string) error + RevokeX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToRevoke string) error TaintJWTKey(ctx context.Context, trustDomainID string, authorityID string) (*common.PublicKey, error) RevokeJWTKey(ctx context.Context, trustDomainID string, authorityID string) (*common.PublicKey, error) diff --git a/pkg/server/datastore/sqlstore/sqlstore.go b/pkg/server/datastore/sqlstore/sqlstore.go index 010d9dfc9e..9886e22f6c 100644 --- a/pkg/server/datastore/sqlstore/sqlstore.go +++ b/pkg/server/datastore/sqlstore/sqlstore.go @@ -3,7 +3,6 @@ package sqlstore import ( "bytes" "context" - "crypto" "crypto/x509" "database/sql" "errors" @@ -25,9 +24,9 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/spire-api-sdk/proto/spire/api/types" "github.com/spiffe/spire/pkg/common/bundleutil" - "github.com/spiffe/spire/pkg/common/cryptoutil" "github.com/spiffe/spire/pkg/common/protoutil" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/x509util" "github.com/spiffe/spire/pkg/server/datastore" "github.com/spiffe/spire/proto/private/server/journal" "github.com/spiffe/spire/proto/spire/common" @@ -246,16 +245,16 @@ func (ds *Plugin) PruneBundle(ctx context.Context, trustDomainID string, expires } // TaintX509CAByKey taints an X.509 CA signed using the provided public key -func (ds *Plugin) TaintX509CA(ctx context.Context, trustDoaminID string, publicKeyToTaint crypto.PublicKey) error { +func (ds *Plugin) TaintX509CA(ctx context.Context, trustDoaminID string, subjectKeyIDToTaint string) error { return ds.withReadModifyWriteTx(ctx, func(tx *gorm.DB) (err error) { - return taintX509CA(tx, trustDoaminID, publicKeyToTaint) + return taintX509CA(tx, trustDoaminID, subjectKeyIDToTaint) }) } // RevokeX509CA removes a Root CA from the bundle -func (ds *Plugin) RevokeX509CA(ctx context.Context, trustDoaminID string, publicKeyToRevoke crypto.PublicKey) error { +func (ds *Plugin) RevokeX509CA(ctx context.Context, trustDoaminID string, subjectKeyIDToRevoke string) error { return ds.withReadModifyWriteTx(ctx, func(tx *gorm.DB) (err error) { - return revokeX509CA(tx, trustDoaminID, publicKeyToRevoke) + return revokeX509CA(tx, trustDoaminID, subjectKeyIDToRevoke) }) } @@ -1149,10 +1148,6 @@ func applyBundleMask(model *Bundle, newBundle *common.Bundle, inputMask *common. bundle.SequenceNumber = newBundle.SequenceNumber } - if inputMask.X509TaintedKeys { - bundle.X509TaintedKeys = newBundle.X509TaintedKeys - } - newModel, err := bundleToModel(bundle) if err != nil { return nil, nil, err @@ -1376,33 +1371,37 @@ func pruneBundle(tx *gorm.DB, trustDomainID string, expiry time.Time, log logrus return changed, nil } -func taintX509CA(tx *gorm.DB, trustDomainID string, publicKeyToTaint crypto.PublicKey) error { +func taintX509CA(tx *gorm.DB, trustDomainID string, subjectKeyIDToTaint string) error { bundle, err := getBundle(tx, trustDomainID) if err != nil { return err } - for _, eachTaintedKey := range bundle.X509TaintedKeys { - taintedKey, err := x509.ParsePKIXPublicKey(eachTaintedKey.PublicKey) + found := false + for _, eachRootCA := range bundle.RootCas { + x509CA, err := x509.ParseCertificate(eachRootCA.DerBytes) if err != nil { - return status.Errorf(codes.Internal, "failed to parse tainted Key: %v", err) + return status.Errorf(codes.Internal, "failed to parse rootCA: %v", err) } - ok, err := cryptoutil.PublicKeyEqual(taintedKey, publicKeyToTaint) - if err != nil { - return status.Errorf(codes.Internal, "failed to compare public key: %v", err) + caSubjectKeyID := x509util.SubjectKeyIDToString(x509CA.SubjectKeyId) + if subjectKeyIDToTaint != caSubjectKeyID { + continue } - if ok { + + if eachRootCA.TaintedKey { return status.Errorf(codes.InvalidArgument, "root CA is already tainted") } + + found = true + eachRootCA.TaintedKey = true } - pKey, err := x509.MarshalPKIXPublicKey(publicKeyToTaint) - if err != nil { - return status.Errorf(codes.InvalidArgument, "failed to marshal public key to taint: %v", err) + if !found { + return status.Error(codes.NotFound, "no ca found with provided subject key ID") } - bundle.X509TaintedKeys = append(bundle.X509TaintedKeys, &common.X509TaintedKey{PublicKey: pKey}) + bundle.SequenceNumber++ _, err = updateBundle(tx, bundle, nil) if err != nil { @@ -1412,41 +1411,13 @@ func taintX509CA(tx *gorm.DB, trustDomainID string, publicKeyToTaint crypto.Publ return nil } -func revokeX509CA(tx *gorm.DB, trustDomainID string, publicKeyToRevoke crypto.PublicKey) error { +func revokeX509CA(tx *gorm.DB, trustDomainID string, subjectKeyIDToRevoke string) error { bundle, err := getBundle(tx, trustDomainID) if err != nil { return err } - var taintedKeyFound bool - var taintedKeys []*common.X509TaintedKey - - for _, eachTaintedKey := range bundle.X509TaintedKeys { - taintedKey, err := x509.ParsePKIXPublicKey(eachTaintedKey.PublicKey) - if err != nil { - return status.Errorf(codes.Internal, "failed to parse tainted Key: %v", err) - } - - ok, err := cryptoutil.PublicKeyEqual(taintedKey, publicKeyToRevoke) - if err != nil { - return status.Errorf(codes.Internal, "failed to compare public key: %v", err) - } - if ok { - taintedKeyFound = true - continue - } - - taintedKeys = append(taintedKeys, eachTaintedKey) - } - - if !taintedKeyFound { - return status.Error(codes.InvalidArgument, "it is not possible to revoke an untainted root CA") - } - bundle.X509TaintedKeys = taintedKeys - - // It is possible to keep bundles on journal, when there is no upstream authority, - // this code will be used to remove a CA bundle that is persisted on datastore, - // only in case it is found + keyFound := false var rootCAs []*common.Certificate for _, ca := range bundle.RootCas { cert, err := x509.ParseCertificate(ca.DerBytes) @@ -1454,17 +1425,24 @@ func revokeX509CA(tx *gorm.DB, trustDomainID string, publicKeyToRevoke crypto.Pu return status.Errorf(codes.Internal, "failed to parse root CA: %v", err) } - ok, err := cryptoutil.PublicKeyEqual(cert.PublicKey, publicKeyToRevoke) - if err != nil { - return status.Errorf(codes.Internal, "failed to compare public key: %v", err) - } - - if ok { + caSubjectKeyID := x509util.SubjectKeyIDToString(cert.SubjectKeyId) + if subjectKeyIDToRevoke == caSubjectKeyID { + if !ca.TaintedKey { + return status.Error(codes.InvalidArgument, "it is not possible to revoke an untainted root CA") + } + keyFound = true continue } + rootCAs = append(rootCAs, ca) } + + if !keyFound { + return status.Error(codes.NotFound, "no root CA found with provided subject key ID") + } + bundle.RootCas = rootCAs + bundle.SequenceNumber++ if _, err := updateBundle(tx, bundle, nil); err != nil { return status.Errorf(codes.Internal, "failed to update bundle: %v", err) @@ -1503,6 +1481,7 @@ func taintJWTKey(tx *gorm.DB, trustDomainID string, authorityID string) (*common return nil, status.Error(codes.NotFound, "no JWT Key found with provided key ID") } + bundle.SequenceNumber++ if _, err := updateBundle(tx, bundle, nil); err != nil { return nil, err } @@ -1542,6 +1521,7 @@ func revokeJWTKey(tx *gorm.DB, trustDomainID string, authorityID string) (*commo return nil, status.Error(codes.NotFound, "no JWT Key found with provided key ID") } + bundle.SequenceNumber++ if _, err := updateBundle(tx, bundle, nil); err != nil { return nil, err } diff --git a/pkg/server/datastore/sqlstore/sqlstore_test.go b/pkg/server/datastore/sqlstore/sqlstore_test.go index 34bce54673..1a831e317d 100644 --- a/pkg/server/datastore/sqlstore/sqlstore_test.go +++ b/pkg/server/datastore/sqlstore/sqlstore_test.go @@ -27,6 +27,7 @@ import ( "github.com/spiffe/spire/pkg/common/protoutil" "github.com/spiffe/spire/pkg/common/telemetry" "github.com/spiffe/spire/pkg/common/util" + "github.com/spiffe/spire/pkg/common/x509util" "github.com/spiffe/spire/pkg/server/datastore" "github.com/spiffe/spire/proto/private/server/journal" "github.com/spiffe/spire/proto/spire/common" @@ -641,148 +642,147 @@ func (s *PluginSuite) TestBundlePrune() { func (s *PluginSuite) TestTaintX509CA() { t := s.T() - // Setup - unusedKey := testkey.NewEC256(t) - // Tainted public key on raw format - certPublicKeyRaw, err := x509.MarshalPKIXPublicKey(s.cert.PublicKey) - require.NoError(t, err) - - // Create new bundle with two certs - bundle := bundleutil.BundleProtoFromRootCAs("spiffe://foo", []*x509.Certificate{s.cert, s.cacert}) - bundle.X509TaintedKeys = []*common.X509TaintedKey{ - {PublicKey: []byte("foh")}, - } + skID := x509util.SubjectKeyIDToString(s.cert.SubjectKeyId) - // Bundle not found - err = s.ds.TaintX509CA(ctx, "spiffe://foo", unusedKey.Public()) - spiretest.RequireGRPCStatus(t, err, codes.NotFound, _notFoundErrMsg) + t.Run("bundle not found", func(t *testing.T) { + err := s.ds.TaintX509CA(ctx, "spiffe://foo", "foo") + spiretest.RequireGRPCStatus(t, err, codes.NotFound, _notFoundErrMsg) + }) - _, err = s.ds.CreateBundle(ctx, bundle) + // Create Malformed CA + bundle := bundleutil.BundleProtoFromRootCAs("spiffe://foo", []*x509.Certificate{{Raw: []byte("bar")}}) + _, err := s.ds.CreateBundle(ctx, bundle) require.NoError(t, err) - // Bundle contains a malformed tainted key - err = s.ds.TaintX509CA(ctx, "spiffe://foo", unusedKey.Public()) - spiretest.RequireGRPCStatusHasPrefix(t, err, codes.Internal, "failed to parse tainted Key:") + t.Run("bundle not found", func(t *testing.T) { + err := s.ds.TaintX509CA(ctx, "spiffe://foo", "foo") + spiretest.RequireGRPCStatus(t, err, codes.Internal, "failed to parse rootCA: x509: malformed certificate") + }) - // Remove malformed tainted key - bundle.X509TaintedKeys = []*common.X509TaintedKey{} - _, err = s.ds.UpdateBundle(ctx, bundle, nil) - require.NoError(t, err) + validateBundle := func(expectSequenceNumber uint64) { + expectedRootCAs := []*common.Certificate{ + {DerBytes: s.cert.Raw, TaintedKey: true}, + {DerBytes: s.cacert.Raw}, + } - // Invalid public key to taint provided - err = s.ds.TaintX509CA(ctx, "spiffe://foo", unusedKey) - spiretest.RequireGRPCStatus(t, err, codes.InvalidArgument, "failed to marshal public key to taint: x509: unsupported public key type: *ecdsa.PrivateKey") + fetchedBundle, err := s.ds.FetchBundle(ctx, "spiffe://foo") + require.NoError(t, err) + require.Equal(t, expectedRootCAs, fetchedBundle.RootCas) + require.Equal(t, expectSequenceNumber, fetchedBundle.SequenceNumber) + } - // Taint successfully - err = s.ds.TaintX509CA(ctx, "spiffe://foo", s.cert.PublicKey) + // Update bundle + bundle = bundleutil.BundleProtoFromRootCAs("spiffe://foo", []*x509.Certificate{s.cert, s.cacert}) + _, err = s.ds.UpdateBundle(ctx, bundle, nil) require.NoError(t, err) - fetchedBundle, err := s.ds.FetchBundle(ctx, "spiffe://foo") - require.NoError(t, err) + t.Run("taint successfully", func(t *testing.T) { + err := s.ds.TaintX509CA(ctx, "spiffe://foo", skID) + require.NoError(t, err) - expectedRootCAs := []*common.Certificate{ - {DerBytes: s.cert.Raw}, - {DerBytes: s.cacert.Raw}, - } + validateBundle(1) + }) - require.Equal(t, expectedRootCAs, fetchedBundle.RootCas) + t.Run("no bundle with provided skID", func(t *testing.T) { + // Not able to taint a tainted CA + err := s.ds.TaintX509CA(ctx, "spiffe://foo", "foo") + spiretest.RequireGRPCStatus(t, err, codes.NotFound, "no ca found with provided subject key ID") - expectedTaintedKeys := []*common.X509TaintedKey{ - {PublicKey: certPublicKeyRaw}, - } - require.Equal(t, expectedTaintedKeys, fetchedBundle.X509TaintedKeys) + // Validate than sequence number is not incremented + validateBundle(1) + }) - // Not able to taint a tainted CA - err = s.ds.TaintX509CA(ctx, "spiffe://foo", s.cert.PublicKey) - spiretest.RequireGRPCStatus(t, err, codes.InvalidArgument, "root CA is already tainted") + t.Run("failed to taint already tainted ca", func(t *testing.T) { + // Not able to taint a tainted CA + err := s.ds.TaintX509CA(ctx, "spiffe://foo", skID) + spiretest.RequireGRPCStatus(t, err, codes.InvalidArgument, "root CA is already tainted") + + // Validate than sequence number is not incremented + validateBundle(1) + }) } func (s *PluginSuite) TestRevokeX509CA() { t := s.T() - // Setup - unusedKey := testkey.NewRSA2048(t) + // SubjectKeyID + certID := x509util.SubjectKeyIDToString(s.cert.SubjectKeyId) - caCertPublicKeyRaw, err := x509.MarshalPKIXPublicKey(s.cacert.PublicKey) - require.NoError(t, err) - - // Tainted public key on raw format - certPublicKeyRaw, err := x509.MarshalPKIXPublicKey(s.cert.PublicKey) - require.NoError(t, err) + // Bundle not found + t.Run("bundle not found", func(t *testing.T) { + err := s.ds.RevokeX509CA(ctx, "spiffe://foo", "foo") + spiretest.RequireGRPCStatus(t, err, codes.NotFound, _notFoundErrMsg) + }) + // Create new bundle with two cert (one valid and one expired) keyForMalformedCert := testkey.NewEC256(t) malformedX509 := &x509.Certificate{ PublicKey: keyForMalformedCert.PublicKey, Raw: []byte("no a certificate"), } - - // Create new bundle with two cert (one valid and one expired) bundle := bundleutil.BundleProtoFromRootCAs("spiffe://foo", []*x509.Certificate{s.cert, s.cacert, malformedX509}) - bundle.X509TaintedKeys = []*common.X509TaintedKey{ - {PublicKey: []byte("foh")}, - } - - // Bundle not found - err = s.ds.RevokeX509CA(ctx, "spiffe://foo", unusedKey.Public()) - spiretest.RequireGRPCStatus(t, err, codes.NotFound, _notFoundErrMsg) - - _, err = s.ds.CreateBundle(ctx, bundle) + _, err := s.ds.CreateBundle(ctx, bundle) require.NoError(t, err) - // Bundle contains a malformed tainted key - err = s.ds.RevokeX509CA(ctx, "spiffe://foo", unusedKey.PublicKey) - spiretest.RequireGRPCStatusHasPrefix(t, err, codes.Internal, "failed to parse tainted Key:") + t.Run("Bundle contains a malformed certificate", func(t *testing.T) { + err := s.ds.RevokeX509CA(ctx, "spiffe://foo", "foo") + spiretest.RequireGRPCStatusHasPrefix(t, err, codes.Internal, "failed to parse root CA: x509: malformed certificate") + }) - // Remove malformed tainted key - bundle.X509TaintedKeys = []*common.X509TaintedKey{} + // Remove malformed certificate + bundle = bundleutil.BundleProtoFromRootCAs("spiffe://foo", []*x509.Certificate{s.cert, s.cacert}) _, err = s.ds.UpdateBundle(ctx, bundle, nil) require.NoError(t, err) - // // No root CA is using provided key - // err = s.ds.RevokeX509CA(ctx, "spiffe://foo", unusedKey.PublicKey) - // spiretest.RequireGRPCStatus(t, err, codes.NotFound, "no root CA found with provided public key") - - // No able to revoke untainted bundles - err = s.ds.RevokeX509CA(ctx, "spiffe://foo", s.cert.PublicKey) - spiretest.RequireGRPCStatus(t, err, codes.InvalidArgument, "it is not possible to revoke an untainted root CA") + originalBundles := []*common.Certificate{ + {DerBytes: s.cert.Raw}, + {DerBytes: s.cacert.Raw}, + } - // Mark cert as tainted - bundle.X509TaintedKeys = []*common.X509TaintedKey{ - {PublicKey: certPublicKeyRaw}, - {PublicKey: caCertPublicKeyRaw}, + validateBundle := func(expectedRootCAs []*common.Certificate, expectSequenceNumber uint64) { + fetchedBundle, err := s.ds.FetchBundle(ctx, "spiffe://foo") + require.NoError(t, err) + require.Equal(t, expectedRootCAs, fetchedBundle.RootCas) + require.Equal(t, expectSequenceNumber, fetchedBundle.SequenceNumber) } - _, err = s.ds.UpdateBundle(ctx, bundle, nil) - require.NoError(t, err) - // Bundle contains a malformed root CA - err = s.ds.RevokeX509CA(ctx, "spiffe://foo", s.cert.PublicKey) - spiretest.RequireGRPCStatus(t, err, codes.Internal, "failed to parse root CA: x509: malformed certificate") + t.Run("No root CA is using provided skID", func(t *testing.T) { + err := s.ds.RevokeX509CA(ctx, "spiffe://foo", "foo") + spiretest.RequireGRPCStatus(t, err, codes.NotFound, "no root CA found with provided subject key ID") - // Remove malformed root CA - bundle.RootCas = []*common.Certificate{ - {DerBytes: s.cert.Raw}, - {DerBytes: s.cacert.Raw}, - } - _, err = s.ds.UpdateBundle(ctx, bundle, nil) - require.NoError(t, err) + validateBundle(originalBundles, 0) + }) - // Revoke successfully - err = s.ds.RevokeX509CA(ctx, "spiffe://foo", s.cert.PublicKey) - require.NoError(t, err) + t.Run("Unable to revoke untainted bundles", func(t *testing.T) { + err := s.ds.RevokeX509CA(ctx, "spiffe://foo", certID) + spiretest.RequireGRPCStatus(t, err, codes.InvalidArgument, "it is not possible to revoke an untainted root CA") - fetchedBundle, err := s.ds.FetchBundle(ctx, "spiffe://foo") + validateBundle(originalBundles, 0) + }) + + // Mark cert as tainted + err = s.ds.TaintX509CA(ctx, "spiffe://foo", certID) require.NoError(t, err) - expectedRootCAs := []*common.Certificate{ - {DerBytes: s.cacert.Raw}, - } - require.Equal(t, expectedRootCAs, fetchedBundle.RootCas) + t.Run("Revoke successfully", func(t *testing.T) { + taintedBundles := []*common.Certificate{ + {DerBytes: s.cert.Raw, TaintedKey: true}, + {DerBytes: s.cacert.Raw}, + } + // Validating precondition, with 2 bundles and sequence + validateBundle(taintedBundles, 1) - expectedTaintedKeys := []*common.X509TaintedKey{ - {PublicKey: caCertPublicKeyRaw}, - } - require.Equal(t, expectedTaintedKeys, fetchedBundle.X509TaintedKeys) + // Revoke + err = s.ds.RevokeX509CA(ctx, "spiffe://foo", certID) + require.NoError(t, err) + + // CA is removed and sequence incremented + expectedRootCAs := []*common.Certificate{ + {DerBytes: s.cacert.Raw}, + } + validateBundle(expectedRootCAs, 2) + }) } func (s *PluginSuite) TestTaintJWTKey() { @@ -790,11 +790,12 @@ func (s *PluginSuite) TestTaintJWTKey() { // Setup // Create new bundle with two JWT Keys bundle := bundleutil.BundleProtoFromRootCAs("spiffe://foo", nil) - bundle.JwtSigningKeys = []*common.PublicKey{ + originalKeys := []*common.PublicKey{ {Kid: "key1"}, {Kid: "key2"}, {Kid: "key2"}, } + bundle.JwtSigningKeys = originalKeys // Bundle not found publicKey, err := s.ds.TaintJWTKey(ctx, "spiffe://foo", "key1") @@ -814,25 +815,37 @@ func (s *PluginSuite) TestTaintJWTKey() { spiretest.RequireGRPCStatus(t, err, codes.NotFound, "no JWT Key found with provided key ID") require.Nil(t, publicKey) + validateBundle := func(expectedKeys []*common.PublicKey, expectSequenceNumber uint64) { + fetchedBundle, err := s.ds.FetchBundle(ctx, "spiffe://foo") + require.NoError(t, err) + + spiretest.RequireProtoListEqual(t, expectedKeys, fetchedBundle.JwtSigningKeys) + require.Equal(t, expectSequenceNumber, fetchedBundle.SequenceNumber) + } + + // Validate no changes + validateBundle(originalKeys, 0) + // Taint successfully publicKey, err = s.ds.TaintJWTKey(ctx, "spiffe://foo", "key1") require.NoError(t, err) require.NotNil(t, publicKey) - fetchedBundle, err := s.ds.FetchBundle(ctx, "spiffe://foo") - require.NoError(t, err) - - expectedKeys := []*common.PublicKey{ + taintedKey := []*common.PublicKey{ {Kid: "key1", TaintedKey: true}, {Kid: "key2"}, {Kid: "key2"}, } - require.Equal(t, expectedKeys, fetchedBundle.JwtSigningKeys) + // Validate expected response + validateBundle(taintedKey, 1) // No able to taint Key again publicKey, err = s.ds.TaintJWTKey(ctx, "spiffe://foo", "key1") spiretest.RequireGRPCStatus(t, err, codes.InvalidArgument, "key is already tainted") require.Nil(t, publicKey) + + // No changes + validateBundle(taintedKey, 1) } func (s *PluginSuite) TestRevokeJWTKey() { @@ -878,23 +891,31 @@ func (s *PluginSuite) TestRevokeJWTKey() { require.Nil(t, publicKey) // Remove duplicated key - bundle.JwtSigningKeys = []*common.PublicKey{ + originalKeys := []*common.PublicKey{ {Kid: "key1"}, {Kid: "key2", TaintedKey: true}, } + bundle.JwtSigningKeys = originalKeys _, err = s.ds.UpdateBundle(ctx, bundle, nil) require.NoError(t, err) + validateBundle := func(expectedKeys []*common.PublicKey, expectSequenceNumber uint64) { + fetchedBundle, err := s.ds.FetchBundle(ctx, "spiffe://foo") + require.NoError(t, err) + + spiretest.RequireProtoListEqual(t, expectedKeys, fetchedBundle.JwtSigningKeys) + require.Equal(t, expectSequenceNumber, fetchedBundle.SequenceNumber) + } + + validateBundle(originalKeys, 0) + // Revoke successfully publicKey, err = s.ds.RevokeJWTKey(ctx, "spiffe://foo", "key2") require.NoError(t, err) require.Equal(t, &common.PublicKey{Kid: "key2", TaintedKey: true}, publicKey) - fetchedBundle, err := s.ds.FetchBundle(ctx, "spiffe://foo") - require.NoError(t, err) - expectedJWTKeys := []*common.PublicKey{{Kid: "key1"}} - require.Equal(t, expectedJWTKeys, fetchedBundle.JwtSigningKeys) + validateBundle(expectedJWTKeys, 1) } func (s *PluginSuite) TestCreateAttestedNode() { diff --git a/pkg/server/plugin/upstreamauthority/awspca/pca.go b/pkg/server/plugin/upstreamauthority/awspca/pca.go index 6973a37d99..e3c596c190 100644 --- a/pkg/server/plugin/upstreamauthority/awspca/pca.go +++ b/pkg/server/plugin/upstreamauthority/awspca/pca.go @@ -263,13 +263,13 @@ func (p *PCAPlugin) MintX509CAAndSubscribe(request *upstreamauthorityv1.MintX509 upstreamRoot := certChain[len(certChain)-1] bundle := x509util.DedupeCertificates([]*x509.Certificate{upstreamRoot}, config.supplementalBundle) - upstreamX509Roots, err := x509certificate.ToPluginProtos(bundle) + upstreamX509Roots, err := x509certificate.ToPluginFromCertificates(bundle) if err != nil { return status.Errorf(codes.Internal, "unable to form response upstream X.509 roots: %v", err) } // All else comprises the chain (including the issued certificate) - x509CAChain, err := x509certificate.ToPluginProtos(append([]*x509.Certificate{cert}, certChain[:len(certChain)-1]...)) + x509CAChain, err := x509certificate.ToPluginFromCertificates(append([]*x509.Certificate{cert}, certChain[:len(certChain)-1]...)) if err != nil { return status.Errorf(codes.Internal, "unable to form response X.509 CA chain: %v", err) } diff --git a/pkg/server/plugin/upstreamauthority/awspca/pca_test.go b/pkg/server/plugin/upstreamauthority/awspca/pca_test.go index f47f1ee522..d3eb384f40 100644 --- a/pkg/server/plugin/upstreamauthority/awspca/pca_test.go +++ b/pkg/server/plugin/upstreamauthority/awspca/pca_test.go @@ -15,6 +15,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/acmpca" acmpcatypes "github.com/aws/aws-sdk-go-v2/service/acmpca/types" + "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" "github.com/spiffe/spire/pkg/common/pemutil" "github.com/spiffe/spire/pkg/server/plugin/upstreamauthority" "github.com/spiffe/spire/proto/spire/common" @@ -292,16 +293,20 @@ func TestMintX509CA(t *testing.T) { getCertificateErr error expectMsgPrefix string expectX509CA []*x509.Certificate - expectX509Authorities []*x509.Certificate + expectX509Authorities []*x509certificate.X509Authority expectTTL time.Duration }{ { - test: "Successful mint", - config: successConfig, - csr: makeCSR("spiffe://example.com/foo"), - preferredTTL: 300 * time.Second, - expectX509CA: []*x509.Certificate{expectCert, intermediateCert}, - expectX509Authorities: []*x509.Certificate{bundleCert}, + test: "Successful mint", + config: successConfig, + csr: makeCSR("spiffe://example.com/foo"), + preferredTTL: 300 * time.Second, + expectX509CA: []*x509.Certificate{expectCert, intermediateCert}, + expectX509Authorities: []*x509certificate.X509Authority{ + { + Certificate: bundleCert, + }, + }, getCertificateCert: encodedCert.String(), getCertificateCertChain: encodedCertChain.String(), }, @@ -315,10 +320,17 @@ func TestMintX509CA(t *testing.T) { AssumeRoleARN: validAssumeRoleARN, SupplementalBundlePath: supplementalBundlePath, }, - csr: makeCSR("spiffe://example.com/foo"), - preferredTTL: 300 * time.Second, - expectX509CA: []*x509.Certificate{expectCert, intermediateCert}, - expectX509Authorities: []*x509.Certificate{bundleCert, supplementalCert[0]}, + csr: makeCSR("spiffe://example.com/foo"), + preferredTTL: 300 * time.Second, + expectX509CA: []*x509.Certificate{expectCert, intermediateCert}, + expectX509Authorities: []*x509certificate.X509Authority{ + { + Certificate: bundleCert, + }, + { + Certificate: supplementalCert[0], + }, + }, getCertificateCert: encodedCert.String(), getCertificateCertChain: encodedCertChain.String(), }, diff --git a/pkg/server/plugin/upstreamauthority/awssecret/awssecret.go b/pkg/server/plugin/upstreamauthority/awssecret/awssecret.go index fcc1b72847..300e90b86f 100644 --- a/pkg/server/plugin/upstreamauthority/awssecret/awssecret.go +++ b/pkg/server/plugin/upstreamauthority/awssecret/awssecret.go @@ -138,12 +138,12 @@ func (p *Plugin) MintX509CAAndSubscribe(request *upstreamauthorityv1.MintX509CAR return status.Errorf(codes.Internal, "unable to sign CSR: %v", err) } - x509CAChain, err := x509certificate.ToPluginProtos(append([]*x509.Certificate{cert}, p.upstreamCerts...)) + x509CAChain, err := x509certificate.ToPluginFromCertificates(append([]*x509.Certificate{cert}, p.upstreamCerts...)) if err != nil { return status.Errorf(codes.Internal, "unable to form response X.509 CA chain: %v", err) } - upstreamX509Roots, err := x509certificate.ToPluginProtos(p.bundleCerts) + upstreamX509Roots, err := x509certificate.ToPluginFromCertificates(p.bundleCerts) if err != nil { return status.Errorf(codes.Internal, "unable to form response upstream X.509 roots: %v", err) } diff --git a/pkg/server/plugin/upstreamauthority/awssecret/awssecret_test.go b/pkg/server/plugin/upstreamauthority/awssecret/awssecret_test.go index 437e2dcbfc..4f426b8f70 100644 --- a/pkg/server/plugin/upstreamauthority/awssecret/awssecret_test.go +++ b/pkg/server/plugin/upstreamauthority/awssecret/awssecret_test.go @@ -10,6 +10,7 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/spire/pkg/common/catalog" + "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" "github.com/spiffe/spire/pkg/common/cryptoutil" "github.com/spiffe/spire/pkg/common/x509svid" "github.com/spiffe/spire/pkg/server/plugin/upstreamauthority" @@ -242,7 +243,9 @@ func TestMintX509CA(t *testing.T) { clk := clock.NewMock(t) certsAndKeys, fakeStorageClientCreator := generateTestData(t, clk) - x509Authority := []*x509.Certificate{certsAndKeys.rootCert} + x509Authority := []*x509certificate.X509Authority{ + {Certificate: certsAndKeys.rootCert}, + } makeCSR := func(spiffeID string) []byte { csr, err := util.NewCSRTemplateWithKey(spiffeID, key) @@ -279,7 +282,7 @@ func TestMintX509CA(t *testing.T) { expectCode codes.Code expectMsgPrefix string expectX509CASpiffeID string - expectedX509Authorities []*x509.Certificate + expectedX509Authorities []*x509certificate.X509Authority expectTTL time.Duration numExpectedCAs int }{ diff --git a/pkg/server/plugin/upstreamauthority/certmanager/certmanager.go b/pkg/server/plugin/upstreamauthority/certmanager/certmanager.go index 25c0cd7dd8..7b565f4ab5 100644 --- a/pkg/server/plugin/upstreamauthority/certmanager/certmanager.go +++ b/pkg/server/plugin/upstreamauthority/certmanager/certmanager.go @@ -208,12 +208,12 @@ func (p *Plugin) MintX509CAAndSubscribe(request *upstreamauthorityv1.MintX509CAR return status.Errorf(codes.Internal, "failed to parse CA certificate: %v", err) } - x509CAChain, err := x509certificate.ToPluginProtos(caChain) + x509CAChain, err := x509certificate.ToPluginFromCertificates(caChain) if err != nil { return status.Errorf(codes.Internal, "unable to form response X.509 CA chain: %v", err) } - upstreamX509Roots, err := x509certificate.ToPluginProtos(upstreamRoot) + upstreamX509Roots, err := x509certificate.ToPluginFromCertificates(upstreamRoot) if err != nil { return status.Errorf(codes.Internal, "unable to form response upstream X.509 roots: %v", err) } diff --git a/pkg/server/plugin/upstreamauthority/certmanager/certmanager_test.go b/pkg/server/plugin/upstreamauthority/certmanager/certmanager_test.go index c722bab2e9..d4115600d8 100644 --- a/pkg/server/plugin/upstreamauthority/certmanager/certmanager_test.go +++ b/pkg/server/plugin/upstreamauthority/certmanager/certmanager_test.go @@ -12,6 +12,7 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/spire/pkg/common/catalog" + "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" "github.com/spiffe/spire/pkg/server/plugin/upstreamauthority" cmapi "github.com/spiffe/spire/pkg/server/plugin/upstreamauthority/certmanager/internal/v1" "github.com/spiffe/spire/proto/spire/common" @@ -58,7 +59,7 @@ func Test_MintX509CA(t *testing.T) { preferredTTL time.Duration updateCR func(t *testing.T, cr *cmapi.CertificateRequest) expectX509CA []*x509.Certificate - expectX509Authorities []*x509.Certificate + expectX509Authorities []*x509certificate.X509Authority expectCode codes.Code expectMsgPrefix string }{ @@ -120,8 +121,10 @@ func Test_MintX509CA(t *testing.T) { cr.Status.Certificate = intermediatePEM cr.Status.CA = rootPEM }, - expectX509CA: []*x509.Certificate{intermediate}, - expectX509Authorities: []*x509.Certificate{root}, + expectX509CA: []*x509.Certificate{intermediate}, + expectX509Authorities: []*x509certificate.X509Authority{ + {Certificate: root}, + }, }, } diff --git a/pkg/server/plugin/upstreamauthority/disk/disk.go b/pkg/server/plugin/upstreamauthority/disk/disk.go index a53d7f1f94..0820d8694d 100644 --- a/pkg/server/plugin/upstreamauthority/disk/disk.go +++ b/pkg/server/plugin/upstreamauthority/disk/disk.go @@ -123,12 +123,12 @@ func (p *Plugin) MintX509CAAndSubscribe(request *upstreamauthorityv1.MintX509CAR return status.Errorf(codes.Internal, "unable to sign CSR: %v", err) } - x509CAChain, err := x509certificate.ToPluginProtos(append([]*x509.Certificate{cert}, upstreamCerts.certChain...)) + x509CAChain, err := x509certificate.ToPluginFromCertificates(append([]*x509.Certificate{cert}, upstreamCerts.certChain...)) if err != nil { return status.Errorf(codes.Internal, "unable to form response X.509 CA chain: %v", err) } - upstreamX509Roots, err := x509certificate.ToPluginProtos(upstreamCerts.trustBundle) + upstreamX509Roots, err := x509certificate.ToPluginFromCertificates(upstreamCerts.trustBundle) if err != nil { return status.Errorf(codes.Internal, "unable to form response upstream X.509 roots: %v", err) } diff --git a/pkg/server/plugin/upstreamauthority/disk/disk_test.go b/pkg/server/plugin/upstreamauthority/disk/disk_test.go index 6d87a00eb4..4b854b22a5 100644 --- a/pkg/server/plugin/upstreamauthority/disk/disk_test.go +++ b/pkg/server/plugin/upstreamauthority/disk/disk_test.go @@ -18,6 +18,7 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/spire/pkg/common/catalog" + "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" "github.com/spiffe/spire/pkg/common/cryptoutil" "github.com/spiffe/spire/pkg/common/pemutil" "github.com/spiffe/spire/pkg/common/x509svid" @@ -163,7 +164,7 @@ func TestMintX509CA(t *testing.T) { assert.Equal(t, tt.expectTTL, ttl, "TTL does not match") } assert.Equal(t, tt.expectX509CA, certChainURIs(x509CA)) - assert.Equal(t, tt.expectedX509Authorities, certChainURIs(x509Authorities)) + assert.Equal(t, tt.expectedX509Authorities, authChainURIs(x509Authorities)) // Plugin does not support streaming back changes so assert the // stream returns EOF. @@ -346,6 +347,14 @@ func certChainURIs(chain []*x509.Certificate) []string { return uris } +func authChainURIs(chain []*x509certificate.X509Authority) []string { + var uris []string + for _, authority := range chain { + uris = append(uris, certURI(authority.Certificate)) + } + return uris +} + func certURI(cert *x509.Certificate) string { if len(cert.URIs) == 1 { return cert.URIs[0].String() diff --git a/pkg/server/plugin/upstreamauthority/gcpcas/gcpcas.go b/pkg/server/plugin/upstreamauthority/gcpcas/gcpcas.go index 865cee5041..15891380d9 100644 --- a/pkg/server/plugin/upstreamauthority/gcpcas/gcpcas.go +++ b/pkg/server/plugin/upstreamauthority/gcpcas/gcpcas.go @@ -333,7 +333,7 @@ func (p *Plugin) mintX509CA(ctx context.Context, csr []byte, preferredTTL int32) fullChain := []*x509.Certificate{cert} fullChain = append(fullChain, certChain[:len(certChain)-1]...) - x509CAChain, err := x509certificate.ToPluginProtos(fullChain) + x509CAChain, err := x509certificate.ToPluginFromCertificates(fullChain) if err != nil { return nil, status.Errorf(codes.Internal, "unable to form response X.509 CA chain: %v", err) } @@ -358,7 +358,7 @@ func (p *Plugin) mintX509CA(ctx context.Context, csr []byte, preferredTTL int32) // We may well have specified multiple paths to the same root. rootBundle = x509util.DedupeCertificates(rootBundle) - upstreamX509Roots, err := x509certificate.ToPluginProtos(rootBundle) + upstreamX509Roots, err := x509certificate.ToPluginFromCertificates(rootBundle) if err != nil { return nil, status.Errorf(codes.Internal, "unable to form response upstream X.509 roots: %v", err) } diff --git a/pkg/server/plugin/upstreamauthority/gcpcas/gcpcas_test.go b/pkg/server/plugin/upstreamauthority/gcpcas/gcpcas_test.go index c672cfca1e..8c5de8a7da 100644 --- a/pkg/server/plugin/upstreamauthority/gcpcas/gcpcas_test.go +++ b/pkg/server/plugin/upstreamauthority/gcpcas/gcpcas_test.go @@ -158,11 +158,11 @@ func TestGcpCAS(t *testing.T) { require.NotNil(t, x509Authorities) // Confirm that we don't have unexpected CAs require.Equal(t, 2, len(x509Authorities)) - require.Equal(t, "caX", x509Authorities[0].Subject.CommonName) - require.Equal(t, "caX", x509Authorities[0].Issuer.CommonName) + require.Equal(t, "caX", x509Authorities[0].Certificate.Subject.CommonName) + require.Equal(t, "caX", x509Authorities[0].Certificate.Issuer.CommonName) // We intentionally return the root externalcaY rather than intermediate caZ - require.Equal(t, "externalcaY", x509Authorities[1].Subject.CommonName) - require.Equal(t, "externalcaY", x509Authorities[1].Issuer.CommonName) + require.Equal(t, "externalcaY", x509Authorities[1].Certificate.Subject.CommonName) + require.Equal(t, "externalcaY", x509Authorities[1].Certificate.Issuer.CommonName) require.NotNil(t, x509CA) require.Equal(t, 1, len(x509CA)) @@ -170,8 +170,8 @@ func TestGcpCAS(t *testing.T) { require.Equal(t, "caX", x509CA[0].Issuer.CommonName) rootPool := x509.NewCertPool() - rootPool.AddCert(x509Authorities[0]) - rootPool.AddCert(x509Authorities[1]) + rootPool.AddCert(x509Authorities[0].Certificate) + rootPool.AddCert(x509Authorities[1].Certificate) var opt x509.VerifyOptions opt.Roots = rootPool res, err := x509CA[0].Verify(opt) diff --git a/pkg/server/plugin/upstreamauthority/spire/fake_handlers_test.go b/pkg/server/plugin/upstreamauthority/spire/fake_handlers_test.go index bc2444c3c3..871f5eca39 100644 --- a/pkg/server/plugin/upstreamauthority/spire/fake_handlers_test.go +++ b/pkg/server/plugin/upstreamauthority/spire/fake_handlers_test.go @@ -166,7 +166,7 @@ func (h *handler) appendKey(key *types.JWTKey) *types.Bundle { return cloneBundle(h.bundle) } -func (h *handler) appendRootCA(rootCA *types.X509Certificate) *types.Bundle { +func (h *handler) appendRootCA(rootCA *types.X509Certificate) *types.Bundle { //nolint: unparam // Keeping return for future use h.mtx.Lock() defer h.mtx.Unlock() h.bundle.X509Authorities = append(h.bundle.X509Authorities, rootCA) diff --git a/pkg/server/plugin/upstreamauthority/spire/spire.go b/pkg/server/plugin/upstreamauthority/spire/spire.go index 1df3d7513a..10838213e1 100644 --- a/pkg/server/plugin/upstreamauthority/spire/spire.go +++ b/pkg/server/plugin/upstreamauthority/spire/spire.go @@ -139,19 +139,21 @@ func (p *Plugin) MintX509CAAndSubscribe(request *upstreamauthorityv1.MintX509CAR } defer p.unsubscribeToPolling() - certChain, roots, err := p.serverClient.newDownstreamX509CA(stream.Context(), request.Csr, request.PreferredTtl) + // TODO: downstream RPC is not returning authority metadata, like tainted bit + // avoid using it for now in favor of a call to get bundle RPC + certChain, _, err := p.serverClient.newDownstreamX509CA(stream.Context(), request.Csr, request.PreferredTtl) if err != nil { return status.Errorf(codes.Internal, "unable to request a new Downstream X509CA: %v", err) } - var bundles []*plugintypes.X509Certificate - for _, cert := range roots { - pluginCert, err := x509certificate.ToPluginProto(cert) - if err != nil { - return status.Errorf(codes.Internal, "failed to parse X.509 authorities: %v", err) - } + serverBundle, err := p.serverClient.getBundle(stream.Context()) + if err != nil { + return status.Errorf(codes.Internal, "failed to fetch bundle from upstream server: %v", err) + } - bundles = append(bundles, pluginCert) + bundles, err := x509certificate.ToPluginFromAPIProtos(serverBundle.X509Authorities) + if err != nil { + return status.Errorf(codes.Internal, "failed to parse X.509 authorities: %v", err) } // Set X509 Authorities @@ -159,7 +161,7 @@ func (p *Plugin) MintX509CAAndSubscribe(request *upstreamauthorityv1.MintX509CAR rootCAs := []*plugintypes.X509Certificate{} - x509CAChain, err := x509certificate.ToPluginProtos(certChain) + x509CAChain, err := x509certificate.ToPluginFromCertificates(certChain) if err != nil { return status.Errorf(codes.Internal, "unable to form response X.509 CA chain: %v", err) } diff --git a/pkg/server/plugin/upstreamauthority/spire/spire_test.go b/pkg/server/plugin/upstreamauthority/spire/spire_test.go index 7b1fa2a753..10f6d60783 100644 --- a/pkg/server/plugin/upstreamauthority/spire/spire_test.go +++ b/pkg/server/plugin/upstreamauthority/spire/spire_test.go @@ -13,6 +13,7 @@ import ( svidv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/svid/v1" "github.com/spiffe/spire-api-sdk/proto/spire/api/types" "github.com/spiffe/spire/pkg/common/catalog" + "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" "github.com/spiffe/spire/pkg/common/cryptoutil" "github.com/spiffe/spire/pkg/common/x509svid" "github.com/spiffe/spire/pkg/server/plugin/upstreamauthority" @@ -140,6 +141,31 @@ func TestMintX509CA(t *testing.T) { serverCertUpdate, _ := ca.CreateX509Certificate( testca.WithID(spiffeid.RequireFromPath(trustDomain, "/another")), ) + serverCertUpdateTainted, _ := ca.CreateX509Certificate( + testca.WithID(spiffeid.RequireFromPath(trustDomain, "/another")), + ) + expectedServerUpdateAuthority := []*x509certificate.X509Authority{ + { + Certificate: serverCertUpdate[0], + }, + { + Certificate: serverCertUpdateTainted[0], + Tainted: true, + }, + } + + certToAuthority := func(certs []*x509.Certificate) []*x509certificate.X509Authority { + var authorities []*x509certificate.X509Authority + for _, eachCert := range certs { + authorities = append(authorities, &x509certificate.X509Authority{ + Certificate: eachCert, + }) + } + return authorities + } + // TODO: since now we can taint authorities may we add this feature + // to go-spiffe? + expectedX509Authorities := certToAuthority(ca.Bundle().X509Authorities()) csr, pubKey, err := util.NewCSRTemplate(trustDomain.IDString()) require.NoError(t, err) @@ -270,7 +296,7 @@ func TestMintX509CA(t *testing.T) { return } - require.Equal(t, ca.X509Bundle().X509Authorities(), x509Authorities) + require.Equal(t, expectedX509Authorities, x509Authorities) wantTTL := c.ttl if wantTTL == 0 { @@ -289,6 +315,7 @@ func TestMintX509CA(t *testing.T) { // the upstream poll frequency twice to ensure the plugin picks up // the change to the bundle. server.sAPIServer.appendRootCA(&types.X509Certificate{Asn1: serverCertUpdate[0].Raw}) + server.sAPIServer.appendRootCA(&types.X509Certificate{Asn1: serverCertUpdateTainted[0].Raw, Tainted: true}) mockClock.Add(upstreamPollFreq) mockClock.Add(upstreamPollFreq) mockClock.Add(internalPollFreq) @@ -297,8 +324,7 @@ func TestMintX509CA(t *testing.T) { bundleUpdateResp, err := stream.RecvUpstreamX509Authorities() require.NoError(t, err) - expectBundles := append(ca.X509Authorities(), serverCertUpdate...) - require.Equal(t, expectBundles, bundleUpdateResp) + require.Equal(t, append(expectedX509Authorities, expectedServerUpdateAuthority...), bundleUpdateResp) // Cancel ctx to stop getting updates cancel() diff --git a/pkg/server/plugin/upstreamauthority/upstreamauthority.go b/pkg/server/plugin/upstreamauthority/upstreamauthority.go index abfa2f9971..a35257c2b2 100644 --- a/pkg/server/plugin/upstreamauthority/upstreamauthority.go +++ b/pkg/server/plugin/upstreamauthority/upstreamauthority.go @@ -6,6 +6,7 @@ import ( "time" "github.com/spiffe/spire/pkg/common/catalog" + "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" "github.com/spiffe/spire/proto/spire/common" ) @@ -20,7 +21,7 @@ type UpstreamAuthority interface { // closed when the caller is no longer interested in updates. If the // upstream authority does not support streaming updates, the stream will // return io.EOF when called. - MintX509CA(ctx context.Context, csr []byte, preferredTTL time.Duration) (x509CA, upstreamX509Authorities []*x509.Certificate, stream UpstreamX509AuthorityStream, err error) + MintX509CA(ctx context.Context, csr []byte, preferredTTL time.Duration) (x509CA []*x509.Certificate, upstreamX509Authorities []*x509certificate.X509Authority, stream UpstreamX509AuthorityStream, err error) // PublishJWTKey publishes the given JWT key with the upstream authority. // Support for this method is optional. Implementations that do not support @@ -39,7 +40,7 @@ type UpstreamX509AuthorityStream interface { // method is called, or the context originally passed into MintX509CA is // canceled. If the function returns an error, no more updates will be // available over the stream. - RecvUpstreamX509Authorities() ([]*x509.Certificate, error) + RecvUpstreamX509Authorities() ([]*x509certificate.X509Authority, error) // Close() closes the stream. It MUST be called by callers of MintX509CA // when they are done with the stream. diff --git a/pkg/server/plugin/upstreamauthority/v1.go b/pkg/server/plugin/upstreamauthority/v1.go index 9ecf7eb6db..7444671858 100644 --- a/pkg/server/plugin/upstreamauthority/v1.go +++ b/pkg/server/plugin/upstreamauthority/v1.go @@ -23,7 +23,7 @@ type V1 struct { // MintX509CA provides the V1 implementation of the UpstreamAuthority // interface method of the same name. -func (v1 *V1) MintX509CA(ctx context.Context, csr []byte, preferredTTL time.Duration) (_, _ []*x509.Certificate, _ UpstreamX509AuthorityStream, err error) { +func (v1 *V1) MintX509CA(ctx context.Context, csr []byte, preferredTTL time.Duration) (_ []*x509.Certificate, _ []*x509certificate.X509Authority, _ UpstreamX509AuthorityStream, err error) { ctx, cancel := context.WithCancel(ctx) defer func() { // Only cancel the context if the function fails. Otherwise the @@ -51,6 +51,7 @@ func (v1 *V1) MintX509CA(ctx context.Context, csr []byte, preferredTTL time.Dura return nil, nil, nil, err } + // TODO: may we add a new type to get upstream authority with metadata? return x509CA, upstreamX509Authorities, &v1UpstreamX509AuthorityStream{v1: v1, stream: stream, cancel: cancel}, nil } @@ -91,7 +92,7 @@ func (v1 *V1) PublishJWTKey(ctx context.Context, jwtKey *common.PublicKey) (_ [] return jwtKeys, &v1UpstreamJWTAuthorityStream{v1: v1, stream: stream, cancel: cancel}, nil } -func (v1 *V1) parseMintX509CAFirstResponse(resp *upstreamauthorityv1.MintX509CAResponse) ([]*x509.Certificate, []*x509.Certificate, error) { +func (v1 *V1) parseMintX509CAFirstResponse(resp *upstreamauthorityv1.MintX509CAResponse) ([]*x509.Certificate, []*x509certificate.X509Authority, error) { x509CA, err := x509certificate.FromPluginProtos(resp.X509CaChain) if err != nil { return nil, nil, v1.Errorf(codes.Internal, "plugin response has malformed X.509 CA chain: %v", err) @@ -99,21 +100,27 @@ func (v1 *V1) parseMintX509CAFirstResponse(resp *upstreamauthorityv1.MintX509CAR if len(x509CA) == 0 { return nil, nil, v1.Error(codes.Internal, "plugin response missing X.509 CA chain") } + + intermediateAuthorities := make([]*x509.Certificate, 0, len(x509CA)) + for _, eachCA := range x509CA { + intermediateAuthorities = append(intermediateAuthorities, eachCA.Certificate) + } + x509Authorities, err := v1.parseX509Authorities(resp.UpstreamX509Roots) if err != nil { return nil, nil, err } - return x509CA, x509Authorities, nil + return intermediateAuthorities, x509Authorities, nil } -func (v1 *V1) parseMintX509CABundleUpdate(resp *upstreamauthorityv1.MintX509CAResponse) ([]*x509.Certificate, error) { +func (v1 *V1) parseMintX509CABundleUpdate(resp *upstreamauthorityv1.MintX509CAResponse) ([]*x509certificate.X509Authority, error) { if len(resp.X509CaChain) > 0 { return nil, v1.Error(codes.Internal, "plugin response has an X.509 CA chain after the first response") } return v1.parseX509Authorities(resp.UpstreamX509Roots) } -func (v1 *V1) parseX509Authorities(rawX509Authorities []*types.X509Certificate) ([]*x509.Certificate, error) { +func (v1 *V1) parseX509Authorities(rawX509Authorities []*types.X509Certificate) ([]*x509certificate.X509Authority, error) { x509Authorities, err := x509certificate.FromPluginProtos(rawX509Authorities) if err != nil { return nil, v1.Errorf(codes.Internal, "plugin response has malformed upstream X.509 roots: %v", err) @@ -145,7 +152,7 @@ type v1UpstreamX509AuthorityStream struct { cancel context.CancelFunc } -func (s *v1UpstreamX509AuthorityStream) RecvUpstreamX509Authorities() ([]*x509.Certificate, error) { +func (s *v1UpstreamX509AuthorityStream) RecvUpstreamX509Authorities() ([]*x509certificate.X509Authority, error) { for { resp, err := s.stream.Recv() switch { diff --git a/pkg/server/plugin/upstreamauthority/v1_test.go b/pkg/server/plugin/upstreamauthority/v1_test.go index f1af3e28cd..159001b0c0 100644 --- a/pkg/server/plugin/upstreamauthority/v1_test.go +++ b/pkg/server/plugin/upstreamauthority/v1_test.go @@ -45,13 +45,23 @@ func TestV1MintX509CA(t *testing.T) { x509CA := upstreamCA.ChildCA() expectedX509CAChain := x509CA.X509Authorities() - expectedUpstreamX509Roots := upstreamCA.X509Authorities() + var expectedUpstreamX509Roots []*x509certificate.X509Authority + for _, eachCert := range upstreamCA.X509Authorities() { + expectedUpstreamX509Roots = append(expectedUpstreamX509Roots, &x509certificate.X509Authority{ + Certificate: eachCert, + }) + } + taintedUpstreamX509Roots := []*x509certificate.X509Authority{ + { + Certificate: expectedUpstreamX509Roots[0].Certificate, + Tainted: true, + }, + } - validX509CAChain := x509certificate.RequireToPluginProtos(expectedX509CAChain) + validX509CAChain := x509certificate.RequireToPluginFromCertificates(expectedX509CAChain) validUpstreamX509Roots := x509certificate.RequireToPluginProtos(expectedUpstreamX509Roots) malformedX509CAChain := []*types.X509Certificate{{Asn1: []byte("OHNO")}} malformedUpstreamX509Roots := []*types.X509Certificate{{Asn1: []byte("OHNO")}} - withoutX509CAChain := &upstreamauthorityv1.MintX509CAResponse{ X509CaChain: nil, UpstreamX509Roots: validUpstreamX509Roots, @@ -72,18 +82,28 @@ func TestV1MintX509CA(t *testing.T) { X509CaChain: validX509CAChain, UpstreamX509Roots: validUpstreamX509Roots, } + withTaintedUpstreamX509Roots := &upstreamauthorityv1.MintX509CAResponse{ + X509CaChain: validX509CAChain, + UpstreamX509Roots: []*types.X509Certificate{ + { + Asn1: validUpstreamX509Roots[0].Asn1, + Tainted: true, + }, + }, + } builder := BuildV1() for _, tt := range []struct { - test string - builder *V1Builder - expectCode codes.Code - expectMessage string - expectStreamUpdates bool - expectStreamCode codes.Code - expectStreamMessage string - expectLogs []spiretest.LogEntry + test string + builder *V1Builder + expectCode codes.Code + expectMessage string + expectStreamUpdates bool + expectStreamCode codes.Code + expectStreamMessage string + expectLogs []spiretest.LogEntry + expectUpstreamX509RootsResponse []*x509certificate.X509Authority }{ { test: "plugin returns before sending first response", @@ -138,6 +158,15 @@ func TestV1MintX509CA(t *testing.T) { expectStreamCode: codes.OK, expectStreamMessage: "", }, + { + test: "success with tainted authority", + builder: builder. + WithMintX509CAResponse(withTaintedUpstreamX509Roots), + expectCode: codes.OK, + expectMessage: "", + expectStreamUpdates: false, + expectUpstreamX509RootsResponse: taintedUpstreamX509Roots, + }, { test: "second plugin response is bad (contains X.509 CA)", builder: builder. @@ -182,8 +211,12 @@ func TestV1MintX509CA(t *testing.T) { } require.NotNil(t, upstreamX509RootsStream, "stream should have been returned") defer upstreamX509RootsStream.Close() + expectUpstreamX509Roots := expectedUpstreamX509Roots + if tt.expectUpstreamX509RootsResponse != nil { + expectUpstreamX509Roots = tt.expectUpstreamX509RootsResponse + } assert.Equal(t, expectedX509CAChain, x509CA) - assert.Equal(t, expectedUpstreamX509Roots, upstreamX509Roots) + assert.Equal(t, expectUpstreamX509Roots, upstreamX509Roots) switch { case !tt.expectStreamUpdates: @@ -193,7 +226,11 @@ func TestV1MintX509CA(t *testing.T) { case tt.expectStreamCode == codes.OK: upstreamX509Roots, err = upstreamX509RootsStream.RecvUpstreamX509Authorities() assert.NoError(t, err, "stream should have returned update") - assert.Equal(t, expectedUpstreamX509Roots, upstreamX509Roots) + expected := expectUpstreamX509Roots + if tt.expectUpstreamX509RootsResponse != nil { + expected = tt.expectUpstreamX509RootsResponse + } + assert.Equal(t, expected, upstreamX509Roots) default: upstreamX509Roots, err = upstreamX509RootsStream.RecvUpstreamX509Authorities() spiretest.RequireGRPCStatusHasPrefix(t, err, tt.expectStreamCode, tt.expectStreamMessage) diff --git a/pkg/server/plugin/upstreamauthority/vault/vault.go b/pkg/server/plugin/upstreamauthority/vault/vault.go index 011df84331..05bb09d057 100644 --- a/pkg/server/plugin/upstreamauthority/vault/vault.go +++ b/pkg/server/plugin/upstreamauthority/vault/vault.go @@ -221,7 +221,7 @@ func (p *Plugin) MintX509CAAndSubscribe(req *upstreamauthorityv1.MintX509CAReque return status.Errorf(codes.Internal, "failed to parse Root CA certificate: %v", err) } - upstreamX509Roots, err := x509certificate.ToPluginProtos([]*x509.Certificate{upstreamRoot}) + upstreamX509Roots, err := x509certificate.ToPluginFromCertificates([]*x509.Certificate{upstreamRoot}) if err != nil { return status.Errorf(codes.Internal, "unable to form response upstream X.509 roots: %v", err) } @@ -249,7 +249,7 @@ func (p *Plugin) MintX509CAAndSubscribe(req *upstreamauthorityv1.MintX509CAReque certChain = append(certChain, b) } - x509CAChain, err := x509certificate.ToPluginProtos(certChain) + x509CAChain, err := x509certificate.ToPluginFromCertificates(certChain) if err != nil { return status.Errorf(codes.Internal, "unable to form response X.509 CA chain: %v", err) } diff --git a/pkg/server/plugin/upstreamauthority/vault/vault_test.go b/pkg/server/plugin/upstreamauthority/vault/vault_test.go index c4db283357..f1913cd546 100644 --- a/pkg/server/plugin/upstreamauthority/vault/vault_test.go +++ b/pkg/server/plugin/upstreamauthority/vault/vault_test.go @@ -16,6 +16,7 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/spire/pkg/common/catalog" + "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" "github.com/spiffe/spire/pkg/common/pemutil" "github.com/spiffe/spire/pkg/server/plugin/upstreamauthority" "github.com/spiffe/spire/proto/spire/common" @@ -713,7 +714,7 @@ func TestMintX509CA(t *testing.T) { x509CAIDs := certChainURIs(x509CA) require.Equal(t, tt.expectX509CA, x509CAIDs) - x509AuthoritiesIDs := certChainURIs(x509Authorities) + x509AuthoritiesIDs := authChainURIs(x509Authorities) require.Equal(t, tt.expectedX509Authorities, x509AuthoritiesIDs) if p.cc.clientParams.Namespace != "" { @@ -842,6 +843,14 @@ func certChainURIs(chain []*x509.Certificate) []string { return uris } +func authChainURIs(chain []*x509certificate.X509Authority) []string { + var uris []string + for _, authority := range chain { + uris = append(uris, certURI(authority.Certificate)) + } + return uris +} + func certURI(cert *x509.Certificate) string { if len(cert.URIs) == 1 { return cert.URIs[0].String() diff --git a/proto/private/server/journal/journal.pb.go b/proto/private/server/journal/journal.pb.go index 85015d7191..e41d5f9973 100644 --- a/proto/private/server/journal/journal.pb.go +++ b/proto/private/server/journal/journal.pb.go @@ -96,6 +96,8 @@ type X509CAEntry struct { AuthorityId string `protobuf:"bytes,6,opt,name=authority_id,json=authorityId,proto3" json:"authority_id,omitempty"` // When the CA expires (unix epoch in seconds) NotAfter int64 `protobuf:"varint,7,opt,name=not_after,json=notAfter,proto3" json:"not_after,omitempty"` + // The X.509 Authority Subject Key Identifier (SKID) + UpstreamAuthorityId string `protobuf:"bytes,8,opt,name=upstream_authority_id,json=upstreamAuthorityId,proto3" json:"upstream_authority_id,omitempty"` } func (x *X509CAEntry) Reset() { @@ -179,6 +181,13 @@ func (x *X509CAEntry) GetNotAfter() int64 { return 0 } +func (x *X509CAEntry) GetUpstreamAuthorityId() string { + if x != nil { + return x.UpstreamAuthorityId + } + return "" +} + type JWTKeyEntry struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -341,7 +350,7 @@ var File_private_server_journal_journal_proto protoreflect.FileDescriptor var file_private_server_journal_journal_proto_rawDesc = []byte{ 0x0a, 0x24, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x6a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xed, 0x01, 0x0a, 0x0b, 0x58, 0x35, 0x30, 0x39, 0x43, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa1, 0x02, 0x0a, 0x0b, 0x58, 0x35, 0x30, 0x39, 0x43, 0x41, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6c, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, @@ -356,34 +365,37 @@ var file_private_server_journal_journal_proto_rawDesc = []byte{ 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6e, 0x6f, - 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x22, 0xd5, 0x01, 0x0a, 0x0b, 0x4a, 0x57, 0x54, 0x4b, 0x65, - 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6c, 0x6f, 0x74, 0x49, 0x64, 0x12, - 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, - 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x69, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x07, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x22, 0x59, - 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x07, 0x78, 0x35, 0x30, - 0x39, 0x43, 0x41, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x58, 0x35, 0x30, - 0x39, 0x43, 0x41, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x78, 0x35, 0x30, 0x39, 0x43, 0x41, - 0x73, 0x12, 0x26, 0x0a, 0x07, 0x6a, 0x77, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x4a, 0x57, 0x54, 0x4b, 0x65, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x07, 0x6a, 0x77, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x2a, 0x38, 0x0a, 0x06, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, - 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, - 0x0a, 0x06, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x03, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x4c, - 0x44, 0x10, 0x04, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x2f, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x2f, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2f, 0x6a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x22, 0xd5, 0x01, 0x0a, 0x0b, 0x4a, + 0x57, 0x54, 0x4b, 0x65, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x6c, + 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6c, 0x6f, + 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x69, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1f, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x07, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x21, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x49, 0x64, 0x22, 0x59, 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x26, 0x0a, + 0x07, 0x78, 0x35, 0x30, 0x39, 0x43, 0x41, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, + 0x2e, 0x58, 0x35, 0x30, 0x39, 0x43, 0x41, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x78, 0x35, + 0x30, 0x39, 0x43, 0x41, 0x73, 0x12, 0x26, 0x0a, 0x07, 0x6a, 0x77, 0x74, 0x4b, 0x65, 0x79, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x4a, 0x57, 0x54, 0x4b, 0x65, 0x79, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6a, 0x77, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x2a, 0x38, 0x0a, + 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x45, 0x44, + 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x03, 0x12, 0x07, + 0x0a, 0x03, 0x4f, 0x4c, 0x44, 0x10, 0x04, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x2f, 0x73, 0x70, 0x69, + 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x6a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/private/server/journal/journal.proto b/proto/private/server/journal/journal.proto index 798339bcb2..41530cb935 100644 --- a/proto/private/server/journal/journal.proto +++ b/proto/private/server/journal/journal.proto @@ -22,6 +22,9 @@ message X509CAEntry { // When the CA expires (unix epoch in seconds) int64 not_after = 7; + + // The X.509 Authority Subject Key Identifier (SKID) + string upstream_authority_id = 8; } message JWTKeyEntry { diff --git a/proto/spire/common/common.pb.go b/proto/spire/common/common.pb.go index ec218ed9cc..78f9bc3d86 100644 --- a/proto/spire/common/common.pb.go +++ b/proto/spire/common/common.pb.go @@ -719,7 +719,8 @@ type Certificate struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - DerBytes []byte `protobuf:"bytes,1,opt,name=der_bytes,json=derBytes,proto3" json:"der_bytes,omitempty"` + DerBytes []byte `protobuf:"bytes,1,opt,name=der_bytes,json=derBytes,proto3" json:"der_bytes,omitempty"` + TaintedKey bool `protobuf:"varint,2,opt,name=tainted_key,json=taintedKey,proto3" json:"tainted_key,omitempty"` } func (x *Certificate) Reset() { @@ -761,6 +762,13 @@ func (x *Certificate) GetDerBytes() []byte { return nil } +func (x *Certificate) GetTaintedKey() bool { + if x != nil { + return x.TaintedKey + } + return false +} + // * PublicKey represents a PKIX encoded public key type PublicKey struct { state protoimpl.MessageState @@ -854,8 +862,6 @@ type Bundle struct { // * sequence number is a monotonically increasing number that is // incremented every time the bundle is updated SequenceNumber uint64 `protobuf:"varint,5,opt,name=sequence_number,json=sequenceNumber,proto3" json:"sequence_number,omitempty"` - // * list of X.509 tainted keys - X509TaintedKeys []*X509TaintedKey `protobuf:"bytes,6,rep,name=x509_tainted_keys,json=x509TaintedKeys,proto3" json:"x509_tainted_keys,omitempty"` } func (x *Bundle) Reset() { @@ -925,61 +931,6 @@ func (x *Bundle) GetSequenceNumber() uint64 { return 0 } -func (x *Bundle) GetX509TaintedKeys() []*X509TaintedKey { - if x != nil { - return x.X509TaintedKeys - } - return nil -} - -type X509TaintedKey struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // * tainted public key - PublicKey []byte `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` -} - -func (x *X509TaintedKey) Reset() { - *x = X509TaintedKey{} - if protoimpl.UnsafeEnabled { - mi := &file_spire_common_common_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *X509TaintedKey) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*X509TaintedKey) ProtoMessage() {} - -func (x *X509TaintedKey) ProtoReflect() protoreflect.Message { - mi := &file_spire_common_common_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use X509TaintedKey.ProtoReflect.Descriptor instead. -func (*X509TaintedKey) Descriptor() ([]byte, []int) { - return file_spire_common_common_proto_rawDescGZIP(), []int{11} -} - -func (x *X509TaintedKey) GetPublicKey() []byte { - if x != nil { - return x.PublicKey - } - return nil -} - type BundleMask struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -995,7 +946,7 @@ type BundleMask struct { func (x *BundleMask) Reset() { *x = BundleMask{} if protoimpl.UnsafeEnabled { - mi := &file_spire_common_common_proto_msgTypes[12] + mi := &file_spire_common_common_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1008,7 +959,7 @@ func (x *BundleMask) String() string { func (*BundleMask) ProtoMessage() {} func (x *BundleMask) ProtoReflect() protoreflect.Message { - mi := &file_spire_common_common_proto_msgTypes[12] + mi := &file_spire_common_common_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1021,7 +972,7 @@ func (x *BundleMask) ProtoReflect() protoreflect.Message { // Deprecated: Use BundleMask.ProtoReflect.Descriptor instead. func (*BundleMask) Descriptor() ([]byte, []int) { - return file_spire_common_common_proto_rawDescGZIP(), []int{12} + return file_spire_common_common_proto_rawDescGZIP(), []int{11} } func (x *BundleMask) GetRootCas() bool { @@ -1075,7 +1026,7 @@ type AttestedNodeMask struct { func (x *AttestedNodeMask) Reset() { *x = AttestedNodeMask{} if protoimpl.UnsafeEnabled { - mi := &file_spire_common_common_proto_msgTypes[13] + mi := &file_spire_common_common_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1088,7 +1039,7 @@ func (x *AttestedNodeMask) String() string { func (*AttestedNodeMask) ProtoMessage() {} func (x *AttestedNodeMask) ProtoReflect() protoreflect.Message { - mi := &file_spire_common_common_proto_msgTypes[13] + mi := &file_spire_common_common_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1101,7 +1052,7 @@ func (x *AttestedNodeMask) ProtoReflect() protoreflect.Message { // Deprecated: Use AttestedNodeMask.ProtoReflect.Descriptor instead. func (*AttestedNodeMask) Descriptor() ([]byte, []int) { - return file_spire_common_common_proto_rawDescGZIP(), []int{13} + return file_spire_common_common_proto_rawDescGZIP(), []int{12} } func (x *AttestedNodeMask) GetAttestationDataType() bool { @@ -1249,75 +1200,69 @@ var file_spire_common_common_proto_rawDesc = []byte{ 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, - 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x2a, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x64, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, - 0x73, 0x22, 0x7a, 0x0a, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1d, - 0x0a, 0x0a, 0x70, 0x6b, 0x69, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x09, 0x70, 0x6b, 0x69, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x69, 0x64, 0x12, - 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, - 0x74, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x74, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x22, 0xbf, 0x02, - 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, - 0x12, 0x34, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x07, 0x72, - 0x6f, 0x6f, 0x74, 0x43, 0x61, 0x73, 0x12, 0x41, 0x0a, 0x10, 0x6a, 0x77, 0x74, 0x5f, 0x73, 0x69, - 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0e, 0x6a, 0x77, 0x74, 0x53, 0x69, - 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, - 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x48, 0x0a, 0x11, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x61, - 0x69, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x58, 0x35, 0x30, 0x39, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x0f, - 0x78, 0x35, 0x30, 0x39, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x73, 0x22, - 0x2f, 0x0a, 0x0e, 0x58, 0x35, 0x30, 0x39, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x4b, 0x65, - 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, - 0x22, 0xc9, 0x01, 0x0a, 0x0a, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x12, - 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x61, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6a, 0x77, - 0x74, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6a, 0x77, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, - 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, - 0x68, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x72, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x71, 0x75, 0x65, - 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0e, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, - 0x12, 0x2a, 0x0a, 0x11, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, - 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x78, 0x35, 0x30, - 0x39, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x9f, 0x02, 0x0a, - 0x10, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x61, 0x73, - 0x6b, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x13, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, - 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x73, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, - 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x63, 0x65, 0x72, - 0x74, 0x4e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x6e, 0x65, 0x77, - 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x6e, 0x65, 0x77, 0x43, 0x65, - 0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2b, - 0x0a, 0x12, 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x61, - 0x66, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6e, 0x65, 0x77, 0x43, - 0x65, 0x72, 0x74, 0x4e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x63, - 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0b, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x42, 0x2c, - 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x69, - 0x66, 0x66, 0x65, 0x2f, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x73, 0x70, 0x69, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x74, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x4b, + 0x65, 0x79, 0x22, 0x7a, 0x0a, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, + 0x1d, 0x0a, 0x0a, 0x70, 0x6b, 0x69, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x6b, 0x69, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x69, 0x64, + 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1f, 0x0a, + 0x0b, 0x74, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x74, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x22, 0xf5, + 0x01, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, + 0x64, 0x12, 0x34, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x61, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x07, + 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x61, 0x73, 0x12, 0x41, 0x0a, 0x10, 0x6a, 0x77, 0x74, 0x5f, 0x73, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0e, 0x6a, 0x77, 0x74, 0x53, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, + 0x0f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, + 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0xc9, 0x01, 0x0a, 0x0a, 0x42, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x61, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x61, 0x73, + 0x12, 0x28, 0x0a, 0x10, 0x6a, 0x77, 0x74, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, + 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6a, 0x77, 0x74, 0x53, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0b, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, + 0x0f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, + 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x74, + 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0f, 0x78, 0x35, 0x30, 0x39, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x4b, 0x65, + 0x79, 0x73, 0x22, 0x9f, 0x02, 0x0a, 0x10, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x4e, + 0x6f, 0x64, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x74, 0x74, 0x65, 0x73, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x63, + 0x65, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x65, 0x72, + 0x74, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x4e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, + 0x33, 0x0a, 0x16, 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x13, 0x6e, 0x65, 0x77, 0x43, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x12, 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x65, 0x72, 0x74, + 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0f, 0x6e, 0x65, 0x77, 0x43, 0x65, 0x72, 0x74, 0x4e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, + 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x74, 0x74, 0x65, 0x73, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x61, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x2f, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x70, 0x69, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1332,7 +1277,7 @@ func file_spire_common_common_proto_rawDescGZIP() []byte { return file_spire_common_common_proto_rawDescData } -var file_spire_common_common_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_spire_common_common_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_spire_common_common_proto_goTypes = []any{ (*Empty)(nil), // 0: spire.common.Empty (*AttestationData)(nil), // 1: spire.common.AttestationData @@ -1345,23 +1290,21 @@ var file_spire_common_common_proto_goTypes = []any{ (*Certificate)(nil), // 8: spire.common.Certificate (*PublicKey)(nil), // 9: spire.common.PublicKey (*Bundle)(nil), // 10: spire.common.Bundle - (*X509TaintedKey)(nil), // 11: spire.common.X509TaintedKey - (*BundleMask)(nil), // 12: spire.common.BundleMask - (*AttestedNodeMask)(nil), // 13: spire.common.AttestedNodeMask + (*BundleMask)(nil), // 11: spire.common.BundleMask + (*AttestedNodeMask)(nil), // 12: spire.common.AttestedNodeMask } var file_spire_common_common_proto_depIdxs = []int32{ - 2, // 0: spire.common.Selectors.entries:type_name -> spire.common.Selector - 2, // 1: spire.common.AttestedNode.selectors:type_name -> spire.common.Selector - 2, // 2: spire.common.RegistrationEntry.selectors:type_name -> spire.common.Selector - 5, // 3: spire.common.RegistrationEntries.entries:type_name -> spire.common.RegistrationEntry - 8, // 4: spire.common.Bundle.root_cas:type_name -> spire.common.Certificate - 9, // 5: spire.common.Bundle.jwt_signing_keys:type_name -> spire.common.PublicKey - 11, // 6: spire.common.Bundle.x509_tainted_keys:type_name -> spire.common.X509TaintedKey - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 2, // 0: spire.common.Selectors.entries:type_name -> spire.common.Selector + 2, // 1: spire.common.AttestedNode.selectors:type_name -> spire.common.Selector + 2, // 2: spire.common.RegistrationEntry.selectors:type_name -> spire.common.Selector + 5, // 3: spire.common.RegistrationEntries.entries:type_name -> spire.common.RegistrationEntry + 8, // 4: spire.common.Bundle.root_cas:type_name -> spire.common.Certificate + 9, // 5: spire.common.Bundle.jwt_signing_keys:type_name -> spire.common.PublicKey + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_spire_common_common_proto_init() } @@ -1503,18 +1446,6 @@ func file_spire_common_common_proto_init() { } } file_spire_common_common_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*X509TaintedKey); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_spire_common_common_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*BundleMask); i { case 0: return &v.state @@ -1526,7 +1457,7 @@ func file_spire_common_common_proto_init() { return nil } } - file_spire_common_common_proto_msgTypes[13].Exporter = func(v any, i int) any { + file_spire_common_common_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*AttestedNodeMask); i { case 0: return &v.state @@ -1545,7 +1476,7 @@ func file_spire_common_common_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_spire_common_common_proto_rawDesc, NumEnums: 0, - NumMessages: 14, + NumMessages: 13, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/spire/common/common.proto b/proto/spire/common/common.proto index b22f752d1c..cd1ed63c61 100644 --- a/proto/spire/common/common.proto +++ b/proto/spire/common/common.proto @@ -125,6 +125,7 @@ message RegistrationEntries { /** Certificate represents a ASN.1/DER encoded X509 certificate */ message Certificate { bytes der_bytes = 1; + bool tainted_key = 2; } /** PublicKey represents a PKIX encoded public key */ @@ -159,14 +160,6 @@ message Bundle { /** sequence number is a monotonically increasing number that is * incremented every time the bundle is updated */ uint64 sequence_number = 5; - - /** list of X.509 tainted keys */ - repeated X509TaintedKey x509_tainted_keys = 6; -} - -message X509TaintedKey { - /** tainted public key*/ - bytes public_key = 1; } message BundleMask { diff --git a/test/fakes/fakedatastore/fakedatastore.go b/test/fakes/fakedatastore/fakedatastore.go index 3e00637dbd..283a2b61ee 100644 --- a/test/fakes/fakedatastore/fakedatastore.go +++ b/test/fakes/fakedatastore/fakedatastore.go @@ -2,7 +2,6 @@ package fakedatastore import ( "context" - "crypto" "fmt" "sort" "sync/atomic" @@ -198,18 +197,18 @@ func (s *DataStore) FetchAttestedNodeEvent(ctx context.Context, eventID uint) (* return s.ds.FetchAttestedNodeEvent(ctx, eventID) } -func (s *DataStore) TaintX509CA(ctx context.Context, trustDomainID string, publicKeyToTaint crypto.PublicKey) error { +func (s *DataStore) TaintX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToTaint string) error { if err := s.getNextError(); err != nil { return err } - return s.ds.TaintX509CA(ctx, trustDomainID, publicKeyToTaint) + return s.ds.TaintX509CA(ctx, trustDomainID, subjectKeyIDToTaint) } -func (s *DataStore) RevokeX509CA(ctx context.Context, trustDomainID string, publicKeyToRevoke crypto.PublicKey) error { +func (s *DataStore) RevokeX509CA(ctx context.Context, trustDomainID string, subjectKeyIDToRevoke string) error { if err := s.getNextError(); err != nil { return err } - return s.ds.RevokeX509CA(ctx, trustDomainID, publicKeyToRevoke) + return s.ds.RevokeX509CA(ctx, trustDomainID, subjectKeyIDToRevoke) } func (s *DataStore) TaintJWTKey(ctx context.Context, trustDomainID string, authorityID string) (*common.PublicKey, error) { diff --git a/test/fakes/fakeupstreamauthority/upstreamauthority.go b/test/fakes/fakeupstreamauthority/upstreamauthority.go index d18519ddd5..7d3915ead5 100644 --- a/test/fakes/fakeupstreamauthority/upstreamauthority.go +++ b/test/fakes/fakeupstreamauthority/upstreamauthority.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" + "errors" "math/big" "sync" "testing" @@ -47,9 +48,9 @@ type UpstreamAuthority struct { x509CAMtx sync.RWMutex x509CA *x509svid.UpstreamCA x509CASN int64 - x509Root *x509.Certificate + x509Root *x509certificate.X509Authority x509Intermediate *x509.Certificate - x509Roots []*x509.Certificate + x509Roots []*x509certificate.X509Authority jwtKeysMtx sync.RWMutex jwtKeys []*common.PublicKey @@ -82,7 +83,7 @@ func (ua *UpstreamAuthority) MintX509CAAndSubscribe(request *upstreamauthorityv1 } if err := ua.sendMintX509CAResponse(stream, &upstreamauthorityv1.MintX509CAResponse{ - X509CaChain: x509certificate.RequireToPluginProtos(x509CAChain), + X509CaChain: x509certificate.RequireToPluginFromCertificates(x509CAChain), UpstreamX509Roots: x509certificate.RequireToPluginProtos(ua.X509Roots()), }); err != nil { return err @@ -140,7 +141,7 @@ func (ua *UpstreamAuthority) RotateX509CA() { caKey = x509IntKey } else { ua.createRootCertificate() - caCert = ua.x509Root + caCert = ua.x509Root.Certificate caKey = x509RootKey } @@ -152,13 +153,30 @@ func (ua *UpstreamAuthority) RotateX509CA() { ua.TriggerX509RootsChanged() } -func (ua *UpstreamAuthority) X509Root() *x509.Certificate { +func (ua *UpstreamAuthority) TaintAuthority(index int) error { + ua.x509CAMtx.Lock() + defer ua.x509CAMtx.Unlock() + + rootsLen := len(ua.x509Roots) + if rootsLen == 0 { + return errors.New("no root to taint") + } + if index >= rootsLen { + return errors.New("out of range") + } + + ua.x509Roots[index].Tainted = true + ua.TriggerX509RootsChanged() + return nil +} + +func (ua *UpstreamAuthority) X509Root() *x509certificate.X509Authority { ua.x509CAMtx.RLock() defer ua.x509CAMtx.RUnlock() return ua.x509Root } -func (ua *UpstreamAuthority) X509Roots() []*x509.Certificate { +func (ua *UpstreamAuthority) X509Roots() []*x509certificate.X509Authority { ua.x509CAMtx.RLock() defer ua.x509CAMtx.RUnlock() return ua.x509Roots @@ -264,7 +282,10 @@ func (ua *UpstreamAuthority) sendPublishJWTKeyStream(stream upstreamauthorityv1. func (ua *UpstreamAuthority) createRootCertificate() { template := createCATemplate("FAKEUPSTREAMAUTHORITY-ROOT", ua.nextX509CASN(), ua.config.KeyUsage) - ua.x509Root = createCertificate(ua.t, template, template, &x509RootKey.PublicKey, x509RootKey) + root := createCertificate(ua.t, template, template, &x509RootKey.PublicKey, x509RootKey) + ua.x509Root = &x509certificate.X509Authority{ + Certificate: root, + } ua.x509Roots = append(ua.x509Roots, ua.x509Root) } @@ -273,7 +294,7 @@ func (ua *UpstreamAuthority) createIntermediateCertificate() { ua.createRootCertificate() } template := createCATemplate("FAKEUPSTREAMAUTHORITY-INT", ua.nextX509CASN(), ua.config.KeyUsage) - ua.x509Intermediate = createCertificate(ua.t, template, ua.x509Root, &x509IntKey.PublicKey, x509RootKey) + ua.x509Intermediate = createCertificate(ua.t, template, ua.x509Root.Certificate, &x509IntKey.PublicKey, x509RootKey) } func (ua *UpstreamAuthority) nextX509CASN() int64 { diff --git a/test/testca/ca.go b/test/testca/ca.go index bfb9904742..442af982a2 100644 --- a/test/testca/ca.go +++ b/test/testca/ca.go @@ -149,6 +149,18 @@ func (ca *CA) JWTBundle() *jwtbundle.Bundle { return jwtbundle.FromJWTAuthorities(ca.td, ca.JWTAuthorities()) } +func (ca *CA) GetSubjectKeyID() string { + return x509util.SubjectKeyIDToString(ca.cert.SubjectKeyId) +} + +func (ca *CA) GetUpstreamAuthorityID() string { + authorityKeyID := ca.cert.AuthorityKeyId + if len(authorityKeyID) == 0 { + return "" + } + return x509util.SubjectKeyIDToString(authorityKeyID) +} + func (ca *CA) chain(includeRoot bool) []*x509.Certificate { chain := []*x509.Certificate{} next := ca @@ -183,7 +195,10 @@ func CreateCACertificate(tb testing.TB, parent *x509.Certificate, parentKey cryp if parent == nil { parent = tmpl parentKey = key + } else { + tmpl.AuthorityKeyId = parent.SubjectKeyId } + return CreateCertificate(tb, tmpl, parent, key.Public(), parentKey), key } diff --git a/test/util/cert_generation.go b/test/util/cert_generation.go index b5290b73a9..0df638ef68 100644 --- a/test/util/cert_generation.go +++ b/test/util/cert_generation.go @@ -12,6 +12,7 @@ import ( "time" "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/spire/pkg/common/x509util" "github.com/spiffe/spire/test/clock" ) @@ -105,6 +106,11 @@ func Sign(req, parent *x509.Certificate, signerPrivateKey any) (*x509.Certificat return nil, nil, err } publicKey = key.Public() + skID, err := x509util.GetSubjectKeyID(publicKey) + if err != nil { + return nil, nil, err + } + req.SubjectKeyId = skID } if signerPrivateKey == nil {