From 0a9b0d4799c82af308ee366dbfcf9b2cc3f4a549 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Sat, 12 Oct 2024 18:08:54 -0700 Subject: [PATCH 01/29] x509pop server plugin support for servers trust bundle Enables the x509pop node attestor server plugin to be configured to use the SPIRE Servers own trust bundle. Signed-off-by: Kevin Fox --- doc/plugin_server_nodeattestor_x509pop.md | 1 + .../plugin/nodeattestor/x509pop/x509pop.go | 101 ++++++++++++++---- 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/doc/plugin_server_nodeattestor_x509pop.md b/doc/plugin_server_nodeattestor_x509pop.md index 121f0ff970..9409d7abf8 100644 --- a/doc/plugin_server_nodeattestor_x509pop.md +++ b/doc/plugin_server_nodeattestor_x509pop.md @@ -18,6 +18,7 @@ spiffe:///spire/agent/x509pop/ | Configuration | Description | Default | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------| +| `spire_trust_bundle` | If true, use the spire servers own trust bundle to use for validation. | | | `ca_bundle_path` | The path to the trusted CA bundle on disk. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. If the CA certificates are in more than one file, use `ca_bundle_paths` instead. | | | `ca_bundle_paths` | A list of paths to trusted CA bundles on disk. The files must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | | `agent_path_template` | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | `"{{ .PluginName}}/{{ .Fingerprint }}"` | diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index d8ba2d7fae..636a1b4c6c 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -5,11 +5,15 @@ import ( "crypto/x509" "encoding/json" "sync" + "time" + "fmt" "github.com/hashicorp/hcl" "github.com/spiffe/go-spiffe/v2/spiffeid" nodeattestorv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/server/nodeattestor/v1" configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" + identityproviderv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/hostservice/server/identityprovider/v1" + "github.com/spiffe/spire-plugin-sdk/pluginsdk" "github.com/spiffe/spire/pkg/common/agentpathtemplate" "github.com/spiffe/spire/pkg/common/catalog" "github.com/spiffe/spire/pkg/common/plugin/x509pop" @@ -35,15 +39,17 @@ func builtin(p *Plugin) catalog.BuiltIn { } type Config struct { + SPIRETrustBundle bool `hcl:"spire_trust_bundle"` CABundlePath string `hcl:"ca_bundle_path"` CABundlePaths []string `hcl:"ca_bundle_paths"` AgentPathTemplate string `hcl:"agent_path_template"` } type configuration struct { - trustDomain spiffeid.TrustDomain - trustBundle *x509.CertPool - pathTemplate *agentpathtemplate.Template + SPIRETrustBundle bool + trustDomain spiffeid.TrustDomain + trustBundle *x509.CertPool + pathTemplate *agentpathtemplate.Template } func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginconf.Status) *configuration { @@ -53,26 +59,28 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco return nil } - var caPaths []string - if hclConfig.CABundlePath != "" && len(hclConfig.CABundlePaths) > 0 { - status.ReportError("only one of ca_bundle_path or ca_bundle_paths can be configured, not both") - } - if hclConfig.CABundlePath != "" { - caPaths = []string{hclConfig.CABundlePath} - } else { - caPaths = hclConfig.CABundlePaths - } - if len(caPaths) == 0 { - status.ReportError("one of ca_bundle_path or ca_bundle_paths must be configured") - } - var trustBundles []*x509.Certificate - for _, caPath := range caPaths { - certs, err := util.LoadCertificates(caPath) - if err != nil { - status.ReportErrorf("unable to load trust bundle %q: %v", caPath, err) + if !hclConfig.SPIRETrustBundle { + var caPaths []string + if hclConfig.CABundlePath != "" && len(hclConfig.CABundlePaths) > 0 { + status.ReportError("only one of ca_bundle_path or ca_bundle_paths can be configured, not both") + } + if hclConfig.CABundlePath != "" { + caPaths = []string{hclConfig.CABundlePath} + } else { + caPaths = hclConfig.CABundlePaths + } + if len(caPaths) == 0 { + status.ReportError("one of ca_bundle_path or ca_bundle_paths must be configured") + } + + for _, caPath := range caPaths { + certs, err := util.LoadCertificates(caPath) + if err != nil { + status.ReportErrorf("unable to load trust bundle %q: %v", caPath, err) + } + trustBundles = append(trustBundles, certs...) } - trustBundles = append(trustBundles, certs...) } pathTemplate := x509pop.DefaultAgentPathTemplate @@ -88,6 +96,7 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco trustDomain: coreConfig.TrustDomain, trustBundle: util.NewCertPool(trustBundles...), pathTemplate: pathTemplate, + SPIRETrustBundle: hclConfig.SPIRETrustBundle, } return newConfig @@ -99,12 +108,51 @@ type Plugin struct { m sync.Mutex config *configuration + identityProvider identityproviderv1.IdentityProviderServiceClient + trustBundle *x509.CertPool } func New() *Plugin { return &Plugin{} } +func (p *Plugin) BrokerHostServices(broker pluginsdk.ServiceBroker) error { + if !broker.BrokerClient(&p.identityProvider) { + return status.Errorf(codes.FailedPrecondition, "IdentityProvider host service is required") + } + go func() { + ctx := context.Background() + sleep := 1 * time.Second + fmt.Printf(" Thingy init\n") + for { + resp, err := p.identityProvider.FetchX509Identity(ctx, &identityproviderv1.FetchX509IdentityRequest{}) + if err != nil { + fmt.Printf(" Thingy: %s\n", err) + } else { + var trustBundles []*x509.Certificate + for _, rawcert := range resp.Bundle.X509Authorities { + certificates, err := x509.ParseCertificates(rawcert.Asn1) + if err == nil { + for _, c := range certificates { + trustBundles = append(trustBundles, c) + } + } + } + if len(trustBundles) > 0 { + fmt.Printf(" Thingy trust bundles found %d\n", len(trustBundles)) + p.m.Lock() + p.trustBundle = util.NewCertPool(trustBundles...) + p.m.Unlock() + sleep = 15 * time.Second + } + } + time.Sleep(sleep) + fmt.Printf(" Thingy loop\n") + } + }() + return nil +} + func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { req, err := stream.Recv() if err != nil { @@ -143,12 +191,21 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { intermediates.AddCert(intermediate) } + trustBundle := config.trustBundle + if config.SPIRETrustBundle { + p.m.Lock() + trustBundle = p.trustBundle + } + // verify the chain of trust chains, err := leaf.Verify(x509.VerifyOptions{ Intermediates: intermediates, - Roots: config.trustBundle, + Roots: trustBundle, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, }) + if config.SPIRETrustBundle { + p.m.Unlock() + } if err != nil { return status.Errorf(codes.PermissionDenied, "certificate verification failed: %v", err) } From 38e985040473c9482e466785885f9ef24f37e8f0 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Sat, 12 Oct 2024 18:35:10 -0700 Subject: [PATCH 02/29] Fix some lint things Signed-off-by: Kevin Fox --- .../plugin/nodeattestor/x509pop/x509pop.go | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index 636a1b4c6c..125dc893db 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -4,16 +4,16 @@ import ( "context" "crypto/x509" "encoding/json" + "fmt" "sync" "time" - "fmt" "github.com/hashicorp/hcl" "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/spire-plugin-sdk/pluginsdk" + identityproviderv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/hostservice/server/identityprovider/v1" nodeattestorv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/server/nodeattestor/v1" configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" - identityproviderv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/hostservice/server/identityprovider/v1" - "github.com/spiffe/spire-plugin-sdk/pluginsdk" "github.com/spiffe/spire/pkg/common/agentpathtemplate" "github.com/spiffe/spire/pkg/common/catalog" "github.com/spiffe/spire/pkg/common/plugin/x509pop" @@ -93,9 +93,9 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco } newConfig := &configuration{ - trustDomain: coreConfig.TrustDomain, - trustBundle: util.NewCertPool(trustBundles...), - pathTemplate: pathTemplate, + trustDomain: coreConfig.TrustDomain, + trustBundle: util.NewCertPool(trustBundles...), + pathTemplate: pathTemplate, SPIRETrustBundle: hclConfig.SPIRETrustBundle, } @@ -106,10 +106,10 @@ type Plugin struct { nodeattestorv1.UnsafeNodeAttestorServer configv1.UnsafeConfigServer - m sync.Mutex - config *configuration - identityProvider identityproviderv1.IdentityProviderServiceClient - trustBundle *x509.CertPool + m sync.Mutex + config *configuration + identityProvider identityproviderv1.IdentityProviderServiceClient + trustBundle *x509.CertPool } func New() *Plugin { @@ -117,10 +117,10 @@ func New() *Plugin { } func (p *Plugin) BrokerHostServices(broker pluginsdk.ServiceBroker) error { - if !broker.BrokerClient(&p.identityProvider) { - return status.Errorf(codes.FailedPrecondition, "IdentityProvider host service is required") - } - go func() { + if !broker.BrokerClient(&p.identityProvider) { + return status.Errorf(codes.FailedPrecondition, "IdentityProvider host service is required") + } + go func() { ctx := context.Background() sleep := 1 * time.Second fmt.Printf(" Thingy init\n") @@ -133,9 +133,7 @@ func (p *Plugin) BrokerHostServices(broker pluginsdk.ServiceBroker) error { for _, rawcert := range resp.Bundle.X509Authorities { certificates, err := x509.ParseCertificates(rawcert.Asn1) if err == nil { - for _, c := range certificates { - trustBundles = append(trustBundles, c) - } + trustBundles = append(trustBundles, certificates...) } } if len(trustBundles) > 0 { @@ -150,7 +148,7 @@ func (p *Plugin) BrokerHostServices(broker pluginsdk.ServiceBroker) error { fmt.Printf(" Thingy loop\n") } }() - return nil + return nil } func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { From b03cd79cddd6176b376d465171c6041fc6b0ef0e Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Mon, 14 Oct 2024 17:16:10 -0700 Subject: [PATCH 03/29] Incorperate feedback Signed-off-by: Kevin Fox --- .../plugin/nodeattestor/x509pop/x509pop.go | 59 ++++++++----------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index 125dc893db..451abd4612 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -4,9 +4,7 @@ import ( "context" "crypto/x509" "encoding/json" - "fmt" "sync" - "time" "github.com/hashicorp/hcl" "github.com/spiffe/go-spiffe/v2/spiffeid" @@ -109,7 +107,6 @@ type Plugin struct { m sync.Mutex config *configuration identityProvider identityproviderv1.IdentityProviderServiceClient - trustBundle *x509.CertPool } func New() *Plugin { @@ -120,34 +117,6 @@ func (p *Plugin) BrokerHostServices(broker pluginsdk.ServiceBroker) error { if !broker.BrokerClient(&p.identityProvider) { return status.Errorf(codes.FailedPrecondition, "IdentityProvider host service is required") } - go func() { - ctx := context.Background() - sleep := 1 * time.Second - fmt.Printf(" Thingy init\n") - for { - resp, err := p.identityProvider.FetchX509Identity(ctx, &identityproviderv1.FetchX509IdentityRequest{}) - if err != nil { - fmt.Printf(" Thingy: %s\n", err) - } else { - var trustBundles []*x509.Certificate - for _, rawcert := range resp.Bundle.X509Authorities { - certificates, err := x509.ParseCertificates(rawcert.Asn1) - if err == nil { - trustBundles = append(trustBundles, certificates...) - } - } - if len(trustBundles) > 0 { - fmt.Printf(" Thingy trust bundles found %d\n", len(trustBundles)) - p.m.Lock() - p.trustBundle = util.NewCertPool(trustBundles...) - p.m.Unlock() - sleep = 15 * time.Second - } - } - time.Sleep(sleep) - fmt.Printf(" Thingy loop\n") - } - }() return nil } @@ -191,8 +160,10 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { trustBundle := config.trustBundle if config.SPIRETrustBundle { - p.m.Lock() - trustBundle = p.trustBundle + trustBundle, err = p.getTrustBundle(stream.Context()) + if err != nil { + return status.Errorf(codes.Internal, "failed to get trust bundle: %v", err) + } } // verify the chain of trust @@ -201,9 +172,6 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { Roots: trustBundle, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, }) - if config.SPIRETrustBundle { - p.m.Unlock() - } if err != nil { return status.Errorf(codes.PermissionDenied, "certificate verification failed: %v", err) } @@ -281,6 +249,25 @@ func (p *Plugin) Validate(_ context.Context, req *configv1.ValidateRequest) (*co }, err } +func (p *Plugin) getTrustBundle(ctx context.Context) (*x509.CertPool, error) { + resp, err := p.identityProvider.FetchX509Identity(ctx, &identityproviderv1.FetchX509IdentityRequest{}) + if err != nil { + return nil, err + } + var trustBundles []*x509.Certificate + for _, rawcert := range resp.Bundle.X509Authorities { + certificates, err := x509.ParseCertificates(rawcert.Asn1) + if err != nil { + return nil, err + } + trustBundles = append(trustBundles, certificates...) + } + if len(trustBundles) > 0 { + return util.NewCertPool(trustBundles...), nil + } + return nil, nil +} + func (p *Plugin) getConfig() (*configuration, error) { p.m.Lock() defer p.m.Unlock() From 0c9aa5448617c20f2a46670382055d5299ea5f81 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 18 Oct 2024 20:01:28 -0700 Subject: [PATCH 04/29] Try to fix test Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go index 59bf072cbb..0d6ee9870d 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go @@ -158,7 +158,9 @@ func (s *Suite) TestAttestFailure() { s.T().Run("not configured", func(t *testing.T) { attestor := new(nodeattestor.V1) - plugintest.Load(t, BuiltIn(), attestor) + plugintest.Load(t, BuiltIn(), attestor, + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider) + ) attestFails(t, attestor, []byte("payload"), codes.FailedPrecondition, "nodeattestor(x509pop): not configured") }) @@ -216,6 +218,7 @@ func (s *Suite) TestConfigure() { doConfig := func(t *testing.T, coreConfig catalog.CoreConfig, config string) error { var err error plugintest.Load(t, BuiltIn(), nil, + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider), plugintest.CaptureConfigureError(&err), plugintest.CoreConfig(coreConfig), plugintest.Configure(config), @@ -271,6 +274,7 @@ func (s *Suite) TestConfigure() { func (s *Suite) loadPlugin(t *testing.T, config string) nodeattestor.NodeAttestor { v1 := new(nodeattestor.V1) plugintest.Load(t, BuiltIn(), v1, + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider), plugintest.CoreConfig(catalog.CoreConfig{ TrustDomain: spiffeid.RequireTrustDomainFromString("example.org"), }), From 6ea18ad485a5e2bc8f24741516a398330b98cf76 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 18 Oct 2024 20:08:48 -0700 Subject: [PATCH 05/29] Try to fix test Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go index 0d6ee9870d..9ebc002634 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go @@ -159,7 +159,7 @@ func (s *Suite) TestAttestFailure() { s.T().Run("not configured", func(t *testing.T) { attestor := new(nodeattestor.V1) plugintest.Load(t, BuiltIn(), attestor, - plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider) + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider)), ) attestFails(t, attestor, []byte("payload"), codes.FailedPrecondition, "nodeattestor(x509pop): not configured") @@ -218,7 +218,7 @@ func (s *Suite) TestConfigure() { doConfig := func(t *testing.T, coreConfig catalog.CoreConfig, config string) error { var err error plugintest.Load(t, BuiltIn(), nil, - plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider), + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider)), plugintest.CaptureConfigureError(&err), plugintest.CoreConfig(coreConfig), plugintest.Configure(config), @@ -274,7 +274,7 @@ func (s *Suite) TestConfigure() { func (s *Suite) loadPlugin(t *testing.T, config string) nodeattestor.NodeAttestor { v1 := new(nodeattestor.V1) plugintest.Load(t, BuiltIn(), v1, - plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider), + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider)), plugintest.CoreConfig(catalog.CoreConfig{ TrustDomain: spiffeid.RequireTrustDomainFromString("example.org"), }), From 01e9c3ffd1507d8b484c1cc2dd768d2d624ff8d5 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 18 Oct 2024 20:16:43 -0700 Subject: [PATCH 06/29] Try to fix test Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go index 9ebc002634..3c3926a376 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go @@ -11,10 +11,12 @@ import ( "testing" "github.com/spiffe/go-spiffe/v2/spiffeid" + identityproviderv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/hostservice/server/identityprovider/v1" "github.com/spiffe/spire/pkg/common/catalog" "github.com/spiffe/spire/pkg/common/plugin/x509pop" "github.com/spiffe/spire/pkg/server/plugin/nodeattestor" "github.com/spiffe/spire/proto/spire/common" + "github.com/spiffe/spire/test/fakes/fakeidentityprovider" "github.com/spiffe/spire/test/fixture" "github.com/spiffe/spire/test/plugintest" "github.com/spiffe/spire/test/spiretest" @@ -159,7 +161,7 @@ func (s *Suite) TestAttestFailure() { s.T().Run("not configured", func(t *testing.T) { attestor := new(nodeattestor.V1) plugintest.Load(t, BuiltIn(), attestor, - plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider)), + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(fakeidentityprovider.New())), ) attestFails(t, attestor, []byte("payload"), codes.FailedPrecondition, "nodeattestor(x509pop): not configured") @@ -218,7 +220,7 @@ func (s *Suite) TestConfigure() { doConfig := func(t *testing.T, coreConfig catalog.CoreConfig, config string) error { var err error plugintest.Load(t, BuiltIn(), nil, - plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider)), + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(fakeidentityprovider.New())), plugintest.CaptureConfigureError(&err), plugintest.CoreConfig(coreConfig), plugintest.Configure(config), @@ -274,7 +276,7 @@ func (s *Suite) TestConfigure() { func (s *Suite) loadPlugin(t *testing.T, config string) nodeattestor.NodeAttestor { v1 := new(nodeattestor.V1) plugintest.Load(t, BuiltIn(), v1, - plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider)), + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(fakeidentityprovider.New())), plugintest.CoreConfig(catalog.CoreConfig{ TrustDomain: spiffeid.RequireTrustDomainFromString("example.org"), }), From fe5bb17656bc77af0056efff9dc652dbd6896974 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 25 Oct 2024 05:42:36 -0700 Subject: [PATCH 07/29] Add some better defaults and doc updates Signed-off-by: Kevin Fox --- doc/plugin_server_nodeattestor_x509pop.md | 17 ++++++++++++++++- pkg/common/plugin/x509pop/x509pop.go | 15 ++++++++++++++- .../plugin/nodeattestor/x509pop/x509pop.go | 5 ++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/doc/plugin_server_nodeattestor_x509pop.md b/doc/plugin_server_nodeattestor_x509pop.md index 9409d7abf8..084b71b8ae 100644 --- a/doc/plugin_server_nodeattestor_x509pop.md +++ b/doc/plugin_server_nodeattestor_x509pop.md @@ -21,7 +21,7 @@ spiffe:///spire/agent/x509pop/ | `spire_trust_bundle` | If true, use the spire servers own trust bundle to use for validation. | | | `ca_bundle_path` | The path to the trusted CA bundle on disk. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. If the CA certificates are in more than one file, use `ca_bundle_paths` instead. | | | `ca_bundle_paths` | A list of paths to trusted CA bundles on disk. The files must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | -| `agent_path_template` | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | `"{{ .PluginName}}/{{ .Fingerprint }}"` | +| `agent_path_template` | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | `See Agent Path Template for details` | A sample configuration: @@ -47,6 +47,20 @@ A sample configuration: ## Agent Path Template The agent path template is a way of customizing the format of generated SPIFFE IDs for agents. + +If using ca_bundle_path(s), the default is: +"{{ .PluginName}}/{{ .Fingerprint }}" + +If using spire_trust_bundle, the default exchanges an SVID under /spire-exchange/* for /spire/agent/x509pop/*, via: +``` +{{- $p := printf "spiffe://%s/spire-exchange/" .TrustDomain }} +{{- if hasPrefix $p .FromSVID }} +{{- printf "/spire/agent/x509pop/%s" (trimPrefix $p .FromSVID) }} +{{- else }} +{{- fail "Invalid SVID" }} +{{- end }} +``` + The template formatter is using Golang text/template conventions, it can reference values provided by the plugin or in a [golang x509.Certificate](https://pkg.go.dev/crypto/x509#Certificate) Some useful values are: @@ -58,3 +72,4 @@ Some useful values are: | .TrustDomain | The configured trust domain | | .Subject.CommonName | The common name field of the agent's x509 certificate | | .SerialNumberHex | The serial number field of the agent's x509 certificate represented as lowercase hexadecimal | +| .FromSVID | The SVID of the first cert if there is one | diff --git a/pkg/common/plugin/x509pop/x509pop.go b/pkg/common/plugin/x509pop/x509pop.go index 51c5e18c8f..dcd6f10b3d 100644 --- a/pkg/common/plugin/x509pop/x509pop.go +++ b/pkg/common/plugin/x509pop/x509pop.go @@ -26,7 +26,14 @@ const ( ) // DefaultAgentPathTemplate is the default template -var DefaultAgentPathTemplate = agentpathtemplate.MustParse("/{{ .PluginName }}/{{ .Fingerprint }}") +var DefaultAgentPathTemplateCN = agentpathtemplate.MustParse("/{{ .PluginName }}/{{ .Fingerprint }}") +var DefaultAgentPathTemplateSVID = agentpathtemplate.MustParse(` +{{- $p := printf "spiffe://%s/spire-exchange/" .TrustDomain }} +{{- if hasPrefix $p .FromSVID }} +{{- printf "/spire/agent/x509pop/%s" (trimPrefix $p .FromSVID) }} +{{- else }} +{{- fail "Invalid SVID" }} +{{- end }}`) type agentPathTemplateData struct { *x509.Certificate @@ -34,6 +41,7 @@ type agentPathTemplateData struct { Fingerprint string PluginName string TrustDomain string + FromSVID string } type AttestationData struct { @@ -267,12 +275,17 @@ func Fingerprint(cert *x509.Certificate) string { // MakeAgentID creates an agent ID from X.509 certificate data. func MakeAgentID(td spiffeid.TrustDomain, agentPathTemplate *agentpathtemplate.Template, cert *x509.Certificate) (spiffeid.ID, error) { + FromSVID := "" + if len(cert.URIs) >= 1 { + FromSVID = cert.URIs[0].String() + } agentPath, err := agentPathTemplate.Execute(agentPathTemplateData{ TrustDomain: td.Name(), Certificate: cert, PluginName: PluginName, SerialNumberHex: SerialNumberHex(cert.SerialNumber), Fingerprint: Fingerprint(cert), + FromSVID: FromSVID, }) if err != nil { return spiffeid.ID{}, err diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index 451abd4612..459ba4c492 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -81,7 +81,10 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco } } - pathTemplate := x509pop.DefaultAgentPathTemplate + pathTemplate := x509pop.DefaultAgentPathTemplateCN + if hclConfig.SPIRETrustBundle { + pathTemplate = x509pop.DefaultAgentPathTemplateSVID + } if len(hclConfig.AgentPathTemplate) > 0 { tmpl, err := agentpathtemplate.Parse(hclConfig.AgentPathTemplate) if err != nil { From 487100d67610ef3cf108adcb969cd050877d358b Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 25 Oct 2024 08:50:48 -0700 Subject: [PATCH 08/29] Incorperate feedback Signed-off-by: Kevin Fox --- doc/plugin_server_nodeattestor_x509pop.md | 23 ++++++++------ pkg/common/plugin/x509pop/x509pop.go | 18 +++-------- .../plugin/nodeattestor/x509pop/x509pop.go | 30 ++++++++++++++++++- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/doc/plugin_server_nodeattestor_x509pop.md b/doc/plugin_server_nodeattestor_x509pop.md index 084b71b8ae..10316d44d8 100644 --- a/doc/plugin_server_nodeattestor_x509pop.md +++ b/doc/plugin_server_nodeattestor_x509pop.md @@ -19,6 +19,7 @@ spiffe:///spire/agent/x509pop/ | Configuration | Description | Default | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------| | `spire_trust_bundle` | If true, use the spire servers own trust bundle to use for validation. | | +| `svid_prefix` | The prefix of the SVID to use for matching vaid SVIDS and exchanging them for Node SVIDs | /spire-exchange | | `ca_bundle_path` | The path to the trusted CA bundle on disk. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. If the CA certificates are in more than one file, use `ca_bundle_paths` instead. | | | `ca_bundle_paths` | A list of paths to trusted CA bundles on disk. The files must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | | `agent_path_template` | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | `See Agent Path Template for details` | @@ -44,6 +45,17 @@ A sample configuration: | SHA1 Fingerprint | `x509pop:ca:fingerprint:0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33` | The SHA1 fingerprint as a hex string for each cert in the PoP chain, excluding the leaf. | | SerialNumber | `x509pop:serialnumber:0a1b2c3d4e5f` | The leaf certificate serial number as a lowercase hexadecimal string | +## SVID Prefix + +When spire_trust_bundle is used, the SPIFFE ID being exchanged must be prefixed by the specified svid_prefix. The prefix will be removed from the .SVIDPath before sending to the +agent path template. + +For example, if your trust domain is example.com and svid_prefix = the default of /spire-exchange, and agent path template is the default, + +spiffe://example.com/spire-exchange/testhost will render out to spiffe://example.com/spire/agent/x509pop/testhost + +If spiffe://example.com/other/testhost is given, it wont match the svid_prefix and it will be rejected. + ## Agent Path Template The agent path template is a way of customizing the format of generated SPIFFE IDs for agents. @@ -52,14 +64,7 @@ If using ca_bundle_path(s), the default is: "{{ .PluginName}}/{{ .Fingerprint }}" If using spire_trust_bundle, the default exchanges an SVID under /spire-exchange/* for /spire/agent/x509pop/*, via: -``` -{{- $p := printf "spiffe://%s/spire-exchange/" .TrustDomain }} -{{- if hasPrefix $p .FromSVID }} -{{- printf "/spire/agent/x509pop/%s" (trimPrefix $p .FromSVID) }} -{{- else }} -{{- fail "Invalid SVID" }} -{{- end }} -``` +"{{ .PluginName}}/{{ .SVIDPath }}" The template formatter is using Golang text/template conventions, it can reference values provided by the plugin or in a [golang x509.Certificate](https://pkg.go.dev/crypto/x509#Certificate) @@ -72,4 +77,4 @@ Some useful values are: | .TrustDomain | The configured trust domain | | .Subject.CommonName | The common name field of the agent's x509 certificate | | .SerialNumberHex | The serial number field of the agent's x509 certificate represented as lowercase hexadecimal | -| .FromSVID | The SVID of the first cert if there is one | +| .SVIDPath | The SVID Path after removing the SVID Prefix | diff --git a/pkg/common/plugin/x509pop/x509pop.go b/pkg/common/plugin/x509pop/x509pop.go index dcd6f10b3d..730264a962 100644 --- a/pkg/common/plugin/x509pop/x509pop.go +++ b/pkg/common/plugin/x509pop/x509pop.go @@ -27,13 +27,7 @@ const ( // DefaultAgentPathTemplate is the default template var DefaultAgentPathTemplateCN = agentpathtemplate.MustParse("/{{ .PluginName }}/{{ .Fingerprint }}") -var DefaultAgentPathTemplateSVID = agentpathtemplate.MustParse(` -{{- $p := printf "spiffe://%s/spire-exchange/" .TrustDomain }} -{{- if hasPrefix $p .FromSVID }} -{{- printf "/spire/agent/x509pop/%s" (trimPrefix $p .FromSVID) }} -{{- else }} -{{- fail "Invalid SVID" }} -{{- end }}`) +var DefaultAgentPathTemplateSVID = agentpathtemplate.MustParse("/{{ .PluginName }}/{{ .SVIDPath }}") type agentPathTemplateData struct { *x509.Certificate @@ -41,7 +35,7 @@ type agentPathTemplateData struct { Fingerprint string PluginName string TrustDomain string - FromSVID string + SVIDPath string } type AttestationData struct { @@ -274,18 +268,14 @@ func Fingerprint(cert *x509.Certificate) string { } // MakeAgentID creates an agent ID from X.509 certificate data. -func MakeAgentID(td spiffeid.TrustDomain, agentPathTemplate *agentpathtemplate.Template, cert *x509.Certificate) (spiffeid.ID, error) { - FromSVID := "" - if len(cert.URIs) >= 1 { - FromSVID = cert.URIs[0].String() - } +func MakeAgentID(td spiffeid.TrustDomain, agentPathTemplate *agentpathtemplate.Template, cert *x509.Certificate, SVIDPath string) (spiffeid.ID, error) { agentPath, err := agentPathTemplate.Execute(agentPathTemplateData{ TrustDomain: td.Name(), Certificate: cert, PluginName: PluginName, SerialNumberHex: SerialNumberHex(cert.SerialNumber), Fingerprint: Fingerprint(cert), - FromSVID: FromSVID, + SVIDPath: SVIDPath, }) if err != nil { return spiffeid.ID{}, err diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index 459ba4c492..fa19773328 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -4,6 +4,7 @@ import ( "context" "crypto/x509" "encoding/json" + "strings" "sync" "github.com/hashicorp/hcl" @@ -38,6 +39,7 @@ func builtin(p *Plugin) catalog.BuiltIn { type Config struct { SPIRETrustBundle bool `hcl:"spire_trust_bundle"` + SPIFFEPrefix *string `hcl:"spiffe_prefix"` CABundlePath string `hcl:"ca_bundle_path"` CABundlePaths []string `hcl:"ca_bundle_paths"` AgentPathTemplate string `hcl:"agent_path_template"` @@ -45,6 +47,7 @@ type Config struct { type configuration struct { SPIRETrustBundle bool + SPIFFEPrefix string trustDomain spiffeid.TrustDomain trustBundle *x509.CertPool pathTemplate *agentpathtemplate.Template @@ -93,11 +96,22 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco pathTemplate = tmpl } + var SPIFFEPrefix string + if hclConfig.SPIFFEPrefix == nil { + SPIFFEPrefix = "/spire-exchange/" + } else { + SPIFFEPrefix = *hclConfig.SPIFFEPrefix + if !strings.HasSuffix(SPIFFEPrefix, "/") { + SPIFFEPrefix += "/" + } + } + newConfig := &configuration{ trustDomain: coreConfig.TrustDomain, trustBundle: util.NewCertPool(trustBundles...), pathTemplate: pathTemplate, SPIRETrustBundle: hclConfig.SPIRETrustBundle, + SPIFFEPrefix: SPIFFEPrefix, } return newConfig @@ -214,7 +228,21 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { return status.Errorf(codes.PermissionDenied, "challenge response verification failed: %v", err) } - spiffeid, err := x509pop.MakeAgentID(config.trustDomain, config.pathTemplate, leaf) + + SVIDPath := "" + if config.SPIRETrustBundle { + if len(leaf.URIs) >= 1 { + SVIDPath = leaf.URIs[0].EscapedPath() + if !strings.HasPrefix(SVIDPath, config.SPIFFEPrefix) { + return status.Errorf(codes.PermissionDenied, "x509 cert doesnt match SPIFFE prefix") + } + } else { + return status.Errorf(codes.PermissionDenied, "valid SVID x509 cert not found") + } + + } + + spiffeid, err := x509pop.MakeAgentID(config.trustDomain, config.pathTemplate, leaf, SVIDPath) if err != nil { return status.Errorf(codes.Internal, "failed to make spiffe id: %v", err) } From 4b06fab2b3d28b284ef223cb0ed9ce77092c9c95 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Sat, 26 Oct 2024 08:09:58 -0700 Subject: [PATCH 09/29] Fix test Signed-off-by: Kevin Fox --- pkg/common/plugin/x509pop/x509pop_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/common/plugin/x509pop/x509pop_test.go b/pkg/common/plugin/x509pop/x509pop_test.go index 4262fc78ba..23ecc43bc1 100644 --- a/pkg/common/plugin/x509pop/x509pop_test.go +++ b/pkg/common/plugin/x509pop/x509pop_test.go @@ -138,7 +138,7 @@ func TestMakeAgentID(t *testing.T) { }{ { desc: "default template with sha1", - template: DefaultAgentPathTemplate, + template: DefaultAgentPathTemplateCN, expectID: "spiffe://example.org/spire/agent/x509pop/da39a3ee5e6b4b0d3255bfef95601890afd80709", }, { @@ -161,7 +161,7 @@ func TestMakeAgentID(t *testing.T) { CommonName: "test-cert", }, } - id, err := MakeAgentID(spiffeid.RequireTrustDomainFromString("example.org"), tt.template, cert) + id, err := MakeAgentID(spiffeid.RequireTrustDomainFromString("example.org"), tt.template, cert, "") if tt.expectErr != "" { require.Error(t, err) require.Contains(t, err.Error(), tt.expectErr) From d55f40d46c89cf1738b33525c3377090eaaf1175 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Sat, 26 Oct 2024 08:21:34 -0700 Subject: [PATCH 10/29] Fix lint Signed-off-by: Kevin Fox --- pkg/common/plugin/x509pop/x509pop.go | 4 ++-- pkg/server/plugin/nodeattestor/x509pop/x509pop.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/common/plugin/x509pop/x509pop.go b/pkg/common/plugin/x509pop/x509pop.go index 730264a962..604599c01e 100644 --- a/pkg/common/plugin/x509pop/x509pop.go +++ b/pkg/common/plugin/x509pop/x509pop.go @@ -268,14 +268,14 @@ func Fingerprint(cert *x509.Certificate) string { } // MakeAgentID creates an agent ID from X.509 certificate data. -func MakeAgentID(td spiffeid.TrustDomain, agentPathTemplate *agentpathtemplate.Template, cert *x509.Certificate, SVIDPath string) (spiffeid.ID, error) { +func MakeAgentID(td spiffeid.TrustDomain, agentPathTemplate *agentpathtemplate.Template, cert *x509.Certificate, svidPath string) (spiffeid.ID, error) { agentPath, err := agentPathTemplate.Execute(agentPathTemplateData{ TrustDomain: td.Name(), Certificate: cert, PluginName: PluginName, SerialNumberHex: SerialNumberHex(cert.SerialNumber), Fingerprint: Fingerprint(cert), - SVIDPath: SVIDPath, + SVIDPath: svidPath, }) if err != nil { return spiffeid.ID{}, err diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index fa19773328..da4cec07df 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -228,7 +228,6 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { return status.Errorf(codes.PermissionDenied, "challenge response verification failed: %v", err) } - SVIDPath := "" if config.SPIRETrustBundle { if len(leaf.URIs) >= 1 { From c12860b0fe52a0ba6ae6b5f6fec639773dfcad5b Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Sat, 26 Oct 2024 09:35:00 -0700 Subject: [PATCH 11/29] Fix lint issue Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index da4cec07df..5e4f60285d 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -238,7 +238,6 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { } else { return status.Errorf(codes.PermissionDenied, "valid SVID x509 cert not found") } - } spiffeid, err := x509pop.MakeAgentID(config.trustDomain, config.pathTemplate, leaf, SVIDPath) From 6ecc0b84bd2a9e4cd66b91e4c1e4f7cca749b4b3 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Sat, 26 Oct 2024 10:34:52 -0700 Subject: [PATCH 12/29] Fix lint issue Signed-off-by: Kevin Fox --- doc/plugin_server_nodeattestor_x509pop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/plugin_server_nodeattestor_x509pop.md b/doc/plugin_server_nodeattestor_x509pop.md index 10316d44d8..74354fe8c6 100644 --- a/doc/plugin_server_nodeattestor_x509pop.md +++ b/doc/plugin_server_nodeattestor_x509pop.md @@ -63,7 +63,7 @@ The agent path template is a way of customizing the format of generated SPIFFE I If using ca_bundle_path(s), the default is: "{{ .PluginName}}/{{ .Fingerprint }}" -If using spire_trust_bundle, the default exchanges an SVID under /spire-exchange/* for /spire/agent/x509pop/*, via: +If using spire_trust_bundle, the default exchanges an SVID under `/spire-exchange/*` for `/spire/agent/x509pop/*`, via: "{{ .PluginName}}/{{ .SVIDPath }}" The template formatter is using Golang text/template conventions, it can reference values provided by the plugin or in a [golang x509.Certificate](https://pkg.go.dev/crypto/x509#Certificate) From d34d912ee21f956b9bd2914be4a95cd6bc0074f6 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Thu, 31 Oct 2024 08:08:00 -0700 Subject: [PATCH 13/29] Trim prefix off automatically Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index 5e4f60285d..a33bf2146e 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -235,6 +235,7 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { if !strings.HasPrefix(SVIDPath, config.SPIFFEPrefix) { return status.Errorf(codes.PermissionDenied, "x509 cert doesnt match SPIFFE prefix") } + SVIDPath = strings.TrimPrefix(SVIDPath, config.SPIFFEPrefix) } else { return status.Errorf(codes.PermissionDenied, "valid SVID x509 cert not found") } From 7a8b778bbb961ff5399f579537af275361946d3b Mon Sep 17 00:00:00 2001 From: kfox1111 Date: Tue, 5 Nov 2024 13:26:08 -0800 Subject: [PATCH 14/29] Apply suggestions from code review Co-authored-by: Andrew Harding Signed-off-by: kfox1111 --- doc/plugin_server_nodeattestor_x509pop.md | 4 ++-- pkg/server/plugin/nodeattestor/x509pop/x509pop.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/plugin_server_nodeattestor_x509pop.md b/doc/plugin_server_nodeattestor_x509pop.md index 74354fe8c6..b2fac0f246 100644 --- a/doc/plugin_server_nodeattestor_x509pop.md +++ b/doc/plugin_server_nodeattestor_x509pop.md @@ -19,7 +19,7 @@ spiffe:///spire/agent/x509pop/ | Configuration | Description | Default | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------| | `spire_trust_bundle` | If true, use the spire servers own trust bundle to use for validation. | | -| `svid_prefix` | The prefix of the SVID to use for matching vaid SVIDS and exchanging them for Node SVIDs | /spire-exchange | +| `svid_prefix` | The prefix of the SVID to use for matching valid SVIDS and exchanging them for Node SVIDs | /spire-exchange | | `ca_bundle_path` | The path to the trusted CA bundle on disk. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. If the CA certificates are in more than one file, use `ca_bundle_paths` instead. | | | `ca_bundle_paths` | A list of paths to trusted CA bundles on disk. The files must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | | `agent_path_template` | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | `See Agent Path Template for details` | @@ -45,7 +45,7 @@ A sample configuration: | SHA1 Fingerprint | `x509pop:ca:fingerprint:0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33` | The SHA1 fingerprint as a hex string for each cert in the PoP chain, excluding the leaf. | | SerialNumber | `x509pop:serialnumber:0a1b2c3d4e5f` | The leaf certificate serial number as a lowercase hexadecimal string | -## SVID Prefix +## SVID Path Prefix When spire_trust_bundle is used, the SPIFFE ID being exchanged must be prefixed by the specified svid_prefix. The prefix will be removed from the .SVIDPath before sending to the agent path template. diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index a33bf2146e..a3e9cac39b 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -228,7 +228,7 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { return status.Errorf(codes.PermissionDenied, "challenge response verification failed: %v", err) } - SVIDPath := "" + svidPath := "" if config.SPIRETrustBundle { if len(leaf.URIs) >= 1 { SVIDPath = leaf.URIs[0].EscapedPath() From de1611a08b7409a8e778b9f955d4b80ec1c408cb Mon Sep 17 00:00:00 2001 From: kfox1111 Date: Tue, 5 Nov 2024 13:30:23 -0800 Subject: [PATCH 15/29] Update doc/plugin_server_nodeattestor_x509pop.md Signed-off-by: kfox1111 --- doc/plugin_server_nodeattestor_x509pop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/plugin_server_nodeattestor_x509pop.md b/doc/plugin_server_nodeattestor_x509pop.md index b2fac0f246..7535cc0253 100644 --- a/doc/plugin_server_nodeattestor_x509pop.md +++ b/doc/plugin_server_nodeattestor_x509pop.md @@ -22,7 +22,7 @@ spiffe:///spire/agent/x509pop/ | `svid_prefix` | The prefix of the SVID to use for matching valid SVIDS and exchanging them for Node SVIDs | /spire-exchange | | `ca_bundle_path` | The path to the trusted CA bundle on disk. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. If the CA certificates are in more than one file, use `ca_bundle_paths` instead. | | | `ca_bundle_paths` | A list of paths to trusted CA bundles on disk. The files must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | -| `agent_path_template` | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | `See Agent Path Template for details` | +| `agent_path_template` | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | `See [Agent Path Template](#agent-path-template) for details` | A sample configuration: From 331f0037aa62ffeb0dc3b57125e686b18fead97a Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Wed, 6 Nov 2024 06:15:08 -0800 Subject: [PATCH 16/29] Incorperate feedback Signed-off-by: Kevin Fox --- doc/plugin_server_nodeattestor_x509pop.md | 8 ++--- pkg/common/plugin/x509pop/x509pop.go | 8 ++--- .../plugin/nodeattestor/x509pop/x509pop.go | 35 ++++++++++--------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/doc/plugin_server_nodeattestor_x509pop.md b/doc/plugin_server_nodeattestor_x509pop.md index 74354fe8c6..15c99bbd7c 100644 --- a/doc/plugin_server_nodeattestor_x509pop.md +++ b/doc/plugin_server_nodeattestor_x509pop.md @@ -48,9 +48,9 @@ A sample configuration: ## SVID Prefix When spire_trust_bundle is used, the SPIFFE ID being exchanged must be prefixed by the specified svid_prefix. The prefix will be removed from the .SVIDPath before sending to the -agent path template. +agent path template. If set to "", all prefixes are allowed and you will want to do limiting logic in in the agent_path_template. -For example, if your trust domain is example.com and svid_prefix = the default of /spire-exchange, and agent path template is the default, +Example: if your trust domain is example.com and svid_prefix = the default of /spire-exchange, and agent path template is the default, spiffe://example.com/spire-exchange/testhost will render out to spiffe://example.com/spire/agent/x509pop/testhost @@ -64,7 +64,7 @@ If using ca_bundle_path(s), the default is: "{{ .PluginName}}/{{ .Fingerprint }}" If using spire_trust_bundle, the default exchanges an SVID under `/spire-exchange/*` for `/spire/agent/x509pop/*`, via: -"{{ .PluginName}}/{{ .SVIDPath }}" +"{{ .PluginName}}/{{ .SVIDPathTrimmed }}" The template formatter is using Golang text/template conventions, it can reference values provided by the plugin or in a [golang x509.Certificate](https://pkg.go.dev/crypto/x509#Certificate) @@ -77,4 +77,4 @@ Some useful values are: | .TrustDomain | The configured trust domain | | .Subject.CommonName | The common name field of the agent's x509 certificate | | .SerialNumberHex | The serial number field of the agent's x509 certificate represented as lowercase hexadecimal | -| .SVIDPath | The SVID Path after removing the SVID Prefix | +| .SVIDPathTrimmed | The SVID Path after trimming off the SVID prefix | diff --git a/pkg/common/plugin/x509pop/x509pop.go b/pkg/common/plugin/x509pop/x509pop.go index 604599c01e..9432374258 100644 --- a/pkg/common/plugin/x509pop/x509pop.go +++ b/pkg/common/plugin/x509pop/x509pop.go @@ -27,7 +27,7 @@ const ( // DefaultAgentPathTemplate is the default template var DefaultAgentPathTemplateCN = agentpathtemplate.MustParse("/{{ .PluginName }}/{{ .Fingerprint }}") -var DefaultAgentPathTemplateSVID = agentpathtemplate.MustParse("/{{ .PluginName }}/{{ .SVIDPath }}") +var DefaultAgentPathTemplateSVID = agentpathtemplate.MustParse("/{{ .PluginName }}/{{ .SVIDPathTrimmed }}") type agentPathTemplateData struct { *x509.Certificate @@ -35,7 +35,7 @@ type agentPathTemplateData struct { Fingerprint string PluginName string TrustDomain string - SVIDPath string + SVIDPathTrimmed string } type AttestationData struct { @@ -268,14 +268,14 @@ func Fingerprint(cert *x509.Certificate) string { } // MakeAgentID creates an agent ID from X.509 certificate data. -func MakeAgentID(td spiffeid.TrustDomain, agentPathTemplate *agentpathtemplate.Template, cert *x509.Certificate, svidPath string) (spiffeid.ID, error) { +func MakeAgentID(td spiffeid.TrustDomain, agentPathTemplate *agentpathtemplate.Template, cert *x509.Certificate, svidPathTrimmed string) (spiffeid.ID, error) { agentPath, err := agentPathTemplate.Execute(agentPathTemplateData{ TrustDomain: td.Name(), Certificate: cert, PluginName: PluginName, SerialNumberHex: SerialNumberHex(cert.SerialNumber), Fingerprint: Fingerprint(cert), - SVIDPath: svidPath, + SVIDPathTrimmed: svidPathTrimmed, }) if err != nil { return spiffeid.ID{}, err diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index a33bf2146e..b828b992bc 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -39,15 +39,15 @@ func builtin(p *Plugin) catalog.BuiltIn { type Config struct { SPIRETrustBundle bool `hcl:"spire_trust_bundle"` - SPIFFEPrefix *string `hcl:"spiffe_prefix"` + SVIDPrefix *string `hcl:"spiffe_prefix"` CABundlePath string `hcl:"ca_bundle_path"` CABundlePaths []string `hcl:"ca_bundle_paths"` AgentPathTemplate string `hcl:"agent_path_template"` } type configuration struct { - SPIRETrustBundle bool - SPIFFEPrefix string + spireTrustBundle bool + svidPrefix string trustDomain spiffeid.TrustDomain trustBundle *x509.CertPool pathTemplate *agentpathtemplate.Template @@ -84,6 +84,10 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco } } + if hclConfig.SPIRETrustBundle && (hclConfig.CABundlePath != "" || len(hclConfig.CABundlePaths) > 0) { + status.ReportError("you can not use spire_trust_bundle along with either ca_bundle_path or ca_bundle_paths") + } + pathTemplate := x509pop.DefaultAgentPathTemplateCN if hclConfig.SPIRETrustBundle { pathTemplate = x509pop.DefaultAgentPathTemplateSVID @@ -110,8 +114,8 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco trustDomain: coreConfig.TrustDomain, trustBundle: util.NewCertPool(trustBundles...), pathTemplate: pathTemplate, - SPIRETrustBundle: hclConfig.SPIRETrustBundle, - SPIFFEPrefix: SPIFFEPrefix, + spireTrustBundle: hclConfig.SPIRETrustBundle, + svidPrefix: SVIDPrefix, } return newConfig @@ -176,7 +180,7 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { } trustBundle := config.trustBundle - if config.SPIRETrustBundle { + if config.spireTrustBundle { trustBundle, err = p.getTrustBundle(stream.Context()) if err != nil { return status.Errorf(codes.Internal, "failed to get trust bundle: %v", err) @@ -228,20 +232,19 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { return status.Errorf(codes.PermissionDenied, "challenge response verification failed: %v", err) } - SVIDPath := "" - if config.SPIRETrustBundle { - if len(leaf.URIs) >= 1 { - SVIDPath = leaf.URIs[0].EscapedPath() - if !strings.HasPrefix(SVIDPath, config.SPIFFEPrefix) { - return status.Errorf(codes.PermissionDenied, "x509 cert doesnt match SPIFFE prefix") - } - SVIDPath = strings.TrimPrefix(SVIDPath, config.SPIFFEPrefix) - } else { + svidPath := "" + if config.spireTrustBundle { + if len(leaf.URIs) == 0 { return status.Errorf(codes.PermissionDenied, "valid SVID x509 cert not found") } + svidPath = leaf.URIs[0].EscapedPath() + if !strings.HasPrefix(svidPath, config.SPIFFEPrefix) { + return status.Errorf(codes.PermissionDenied, "x509 cert doesnt match SPIFFE prefix") + } + svidPath = strings.TrimPrefix(svidPath, config.SPIFFEPrefix) } - spiffeid, err := x509pop.MakeAgentID(config.trustDomain, config.pathTemplate, leaf, SVIDPath) + spiffeid, err := x509pop.MakeAgentID(config.trustDomain, config.pathTemplate, leaf, svidPath) if err != nil { return status.Errorf(codes.Internal, "failed to make spiffe id: %v", err) } From 941236b2733ba9bd5d0bef4191cb8094049ab038 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Wed, 6 Nov 2024 06:22:19 -0800 Subject: [PATCH 17/29] Incorperate feedback Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index b828b992bc..a9ad6b83ec 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -100,13 +100,13 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco pathTemplate = tmpl } - var SPIFFEPrefix string - if hclConfig.SPIFFEPrefix == nil { - SPIFFEPrefix = "/spire-exchange/" + var spiffePrefix string + if hclConfig.SVIDPrefix == nil { + svidPrefix = "/spire-exchange/" } else { - SPIFFEPrefix = *hclConfig.SPIFFEPrefix - if !strings.HasSuffix(SPIFFEPrefix, "/") { - SPIFFEPrefix += "/" + svidPrefix = *hclConfig.svidPrefix + if !strings.HasSuffix(svidPrefix, "/") { + svidPrefix += "/" } } @@ -115,7 +115,7 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco trustBundle: util.NewCertPool(trustBundles...), pathTemplate: pathTemplate, spireTrustBundle: hclConfig.SPIRETrustBundle, - svidPrefix: SVIDPrefix, + svidPrefix: svidPrefix, } return newConfig From bdf284a8bd086e8e12e0c3a5d218e75babb14896 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Wed, 6 Nov 2024 06:24:07 -0800 Subject: [PATCH 18/29] Incorperate feedback Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index a9ad6b83ec..bb827e8a19 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -100,11 +100,11 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco pathTemplate = tmpl } - var spiffePrefix string + var svidPrefix string if hclConfig.SVIDPrefix == nil { svidPrefix = "/spire-exchange/" } else { - svidPrefix = *hclConfig.svidPrefix + svidPrefix = *hclConfig.SVIDPrefix if !strings.HasSuffix(svidPrefix, "/") { svidPrefix += "/" } @@ -238,10 +238,10 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { return status.Errorf(codes.PermissionDenied, "valid SVID x509 cert not found") } svidPath = leaf.URIs[0].EscapedPath() - if !strings.HasPrefix(svidPath, config.SPIFFEPrefix) { - return status.Errorf(codes.PermissionDenied, "x509 cert doesnt match SPIFFE prefix") + if !strings.HasPrefix(svidPath, config.svidPrefix) { + return status.Errorf(codes.PermissionDenied, "x509 cert doesnt match SVID prefix") } - svidPath = strings.TrimPrefix(svidPath, config.SPIFFEPrefix) + svidPath = strings.TrimPrefix(svidPath, config.svidPrefix) } spiffeid, err := x509pop.MakeAgentID(config.trustDomain, config.pathTemplate, leaf, svidPath) From 2f1166da5f104d9c03ac8617a04519f80cf6297b Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Wed, 6 Nov 2024 06:39:57 -0800 Subject: [PATCH 19/29] Incorperate feedback Signed-off-by: Kevin Fox --- doc/plugin_server_nodeattestor_x509pop.md | 12 +++++----- .../plugin/nodeattestor/x509pop/x509pop.go | 24 ++++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/doc/plugin_server_nodeattestor_x509pop.md b/doc/plugin_server_nodeattestor_x509pop.md index 47961aded3..a74479cf3d 100644 --- a/doc/plugin_server_nodeattestor_x509pop.md +++ b/doc/plugin_server_nodeattestor_x509pop.md @@ -17,11 +17,11 @@ spiffe:///spire/agent/x509pop/ ``` | Configuration | Description | Default | -|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------| -| `spire_trust_bundle` | If true, use the spire servers own trust bundle to use for validation. | | -| `svid_prefix` | The prefix of the SVID to use for matching valid SVIDS and exchanging them for Node SVIDs | /spire-exchange | -| `ca_bundle_path` | The path to the trusted CA bundle on disk. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. If the CA certificates are in more than one file, use `ca_bundle_paths` instead. | | -| `ca_bundle_paths` | A list of paths to trusted CA bundles on disk. The files must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | +|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------| +| `mode` | If `spiffe`, use the spire servers own trust bundle to use for validation. If `external_pki`, use the specified CA(s). | external_pki | +| `svid_prefix` | The prefix of the SVID to use for matching valid SVIDS and exchanging them for Node SVIDs | /spire-exchange | +| `ca_bundle_path` | The path to the trusted CA bundle on disk. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. If the CA certificates are in more than one file, use `ca_bundle_paths` instead. | | +| `ca_bundle_paths` | A list of paths to trusted CA bundles on disk. The files must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | | `agent_path_template` | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | `See [Agent Path Template](#agent-path-template) for details` | A sample configuration: @@ -47,7 +47,7 @@ A sample configuration: ## SVID Path Prefix -When spire_trust_bundle is used, the SPIFFE ID being exchanged must be prefixed by the specified svid_prefix. The prefix will be removed from the .SVIDPath before sending to the +When mode="spiffe", the SPIFFE ID being exchanged must be prefixed by the specified svid_prefix. The prefix will be removed from the .SVIDPathTrimmed property before sending to the agent path template. If set to "", all prefixes are allowed and you will want to do limiting logic in in the agent_path_template. Example: if your trust domain is example.com and svid_prefix = the default of /spire-exchange, and agent path template is the default, diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index bb827e8a19..b07333e636 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -38,7 +38,7 @@ func builtin(p *Plugin) catalog.BuiltIn { } type Config struct { - SPIRETrustBundle bool `hcl:"spire_trust_bundle"` + Mode string `hcl:"mode"` SVIDPrefix *string `hcl:"spiffe_prefix"` CABundlePath string `hcl:"ca_bundle_path"` CABundlePaths []string `hcl:"ca_bundle_paths"` @@ -46,7 +46,7 @@ type Config struct { } type configuration struct { - spireTrustBundle bool + mode string svidPrefix string trustDomain spiffeid.TrustDomain trustBundle *x509.CertPool @@ -60,8 +60,14 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco return nil } + if hclConfig.Mode == "" { + hclConfig.Mode = "external_pki" + } + if hclConfig.Mode != "external_pki" && hclConfig.Mode != "spiffe" { + status.ReportError("mode can only be either spiffe or external_pki") + } var trustBundles []*x509.Certificate - if !hclConfig.SPIRETrustBundle { + if hclConfig.Mode == "external_pki" { var caPaths []string if hclConfig.CABundlePath != "" && len(hclConfig.CABundlePaths) > 0 { status.ReportError("only one of ca_bundle_path or ca_bundle_paths can be configured, not both") @@ -84,12 +90,12 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco } } - if hclConfig.SPIRETrustBundle && (hclConfig.CABundlePath != "" || len(hclConfig.CABundlePaths) > 0) { - status.ReportError("you can not use spire_trust_bundle along with either ca_bundle_path or ca_bundle_paths") + if hclConfig.Mode == "spiffe" && (hclConfig.CABundlePath != "" || len(hclConfig.CABundlePaths) > 0) { + status.ReportError("you can not use ca_bundle_path or ca_bundle_paths in spiffe mode") } pathTemplate := x509pop.DefaultAgentPathTemplateCN - if hclConfig.SPIRETrustBundle { + if hclConfig.Mode == "spiffe" { pathTemplate = x509pop.DefaultAgentPathTemplateSVID } if len(hclConfig.AgentPathTemplate) > 0 { @@ -114,7 +120,7 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco trustDomain: coreConfig.TrustDomain, trustBundle: util.NewCertPool(trustBundles...), pathTemplate: pathTemplate, - spireTrustBundle: hclConfig.SPIRETrustBundle, + mode: hclConfig.Mode, svidPrefix: svidPrefix, } @@ -180,7 +186,7 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { } trustBundle := config.trustBundle - if config.spireTrustBundle { + if config.mode == "spiffe" { trustBundle, err = p.getTrustBundle(stream.Context()) if err != nil { return status.Errorf(codes.Internal, "failed to get trust bundle: %v", err) @@ -233,7 +239,7 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { } svidPath := "" - if config.spireTrustBundle { + if config.mode == "spiffe" { if len(leaf.URIs) == 0 { return status.Errorf(codes.PermissionDenied, "valid SVID x509 cert not found") } From aab491ac135477b38184a1f446ca734920491ac9 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Wed, 6 Nov 2024 07:34:56 -0800 Subject: [PATCH 20/29] fmt Signed-off-by: Kevin Fox --- .../plugin/nodeattestor/x509pop/x509pop.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index b07333e636..1e63d04443 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -46,11 +46,11 @@ type Config struct { } type configuration struct { - mode string - svidPrefix string - trustDomain spiffeid.TrustDomain - trustBundle *x509.CertPool - pathTemplate *agentpathtemplate.Template + mode string + svidPrefix string + trustDomain spiffeid.TrustDomain + trustBundle *x509.CertPool + pathTemplate *agentpathtemplate.Template } func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginconf.Status) *configuration { @@ -117,11 +117,11 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco } newConfig := &configuration{ - trustDomain: coreConfig.TrustDomain, - trustBundle: util.NewCertPool(trustBundles...), - pathTemplate: pathTemplate, - mode: hclConfig.Mode, - svidPrefix: svidPrefix, + trustDomain: coreConfig.TrustDomain, + trustBundle: util.NewCertPool(trustBundles...), + pathTemplate: pathTemplate, + mode: hclConfig.Mode, + svidPrefix: svidPrefix, } return newConfig From 9f6b9f7add5f83415cc02c7fe86ea1a4f5122daf Mon Sep 17 00:00:00 2001 From: kfox1111 Date: Tue, 10 Dec 2024 06:52:20 -0800 Subject: [PATCH 21/29] Apply suggestions from code review Co-authored-by: Marcos Yacob Signed-off-by: kfox1111 --- doc/plugin_server_nodeattestor_x509pop.md | 4 ++-- pkg/server/plugin/nodeattestor/x509pop/x509pop.go | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/plugin_server_nodeattestor_x509pop.md b/doc/plugin_server_nodeattestor_x509pop.md index 5c6d824177..92d57de6d2 100644 --- a/doc/plugin_server_nodeattestor_x509pop.md +++ b/doc/plugin_server_nodeattestor_x509pop.md @@ -61,10 +61,10 @@ If spiffe://example.com/other/testhost is given, it wont match the svid_prefix a The agent path template is a way of customizing the format of generated SPIFFE IDs for agents. If using ca_bundle_path(s), the default is: -"{{ .PluginName}}/{{ .Fingerprint }}" +"{{ .PluginName }}/{{ .Fingerprint }}" If using spire_trust_bundle, the default exchanges an SVID under `/spire-exchange/*` for `/spire/agent/x509pop/*`, via: -"{{ .PluginName}}/{{ .SVIDPathTrimmed }}" +"{{ .PluginName }}/{{ .SVIDPathTrimmed }}" The template formatter is using Golang text/template conventions, it can reference values provided by the plugin or in a [golang x509.Certificate](https://pkg.go.dev/crypto/x509#Certificate) Details about the template engine are available [here](template_engine.md). diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index 1e63d04443..d971afc228 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -106,10 +106,8 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco pathTemplate = tmpl } - var svidPrefix string - if hclConfig.SVIDPrefix == nil { - svidPrefix = "/spire-exchange/" - } else { + svidPrefix := "/spire-exchange/" + if hclConfig.SVIDPrefix != nil { svidPrefix = *hclConfig.SVIDPrefix if !strings.HasSuffix(svidPrefix, "/") { svidPrefix += "/" From e4313e3a2d7a0d85666410b6583fec2b471f260b Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Thu, 12 Dec 2024 06:25:32 -0800 Subject: [PATCH 22/29] Add logging Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index d971afc228..f824ef4b85 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -7,6 +7,7 @@ import ( "strings" "sync" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/hcl" "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/spire-plugin-sdk/pluginsdk" @@ -129,6 +130,8 @@ type Plugin struct { nodeattestorv1.UnsafeNodeAttestorServer configv1.UnsafeConfigServer + log hclog.Logger + m sync.Mutex config *configuration identityProvider identityproviderv1.IdentityProviderServiceClient @@ -286,6 +289,11 @@ func (p *Plugin) Validate(_ context.Context, req *configv1.ValidateRequest) (*co }, err } +// SetLogger sets this plugin's logger +func (p *Plugin) SetLogger(log hclog.Logger) { + p.log = log +} + func (p *Plugin) getTrustBundle(ctx context.Context) (*x509.CertPool, error) { resp, err := p.identityProvider.FetchX509Identity(ctx, &identityproviderv1.FetchX509IdentityRequest{}) if err != nil { @@ -302,6 +310,7 @@ func (p *Plugin) getTrustBundle(ctx context.Context) (*x509.CertPool, error) { if len(trustBundles) > 0 { return util.NewCertPool(trustBundles...), nil } + p.log.Warn("No trust bundle retrieved from SPIRE") return nil, nil } From c599b12102fde47693467884aa44b284e35bda17 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Thu, 12 Dec 2024 06:53:34 -0800 Subject: [PATCH 23/29] Add some tests Signed-off-by: Kevin Fox --- .../nodeattestor/x509pop/x509pop_test.go | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go index 3c3926a376..96f49a8fc2 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go @@ -271,6 +271,24 @@ func (s *Suite) TestConfigure() { spiretest.RequireGRPCStatusContains(t, err, codes.InvalidArgument, "unable to load trust bundle") }) + + s.T().Run("bad mode and ca_bundle_paths", func(t *testing.T) { + err := doConfig(t, coreConfig, ` + mode = "spiffe" + ca_bundle_paths = ["blah"] + `) + + spiretest.RequireGRPCStatusContains(t, err, codes.InvalidArgument, "you can not use ca_bundle_path or ca_bundle_paths in spiffe mode") + }) + + s.T().Run("bad mode and ca_bundle_path", func(t *testing.T) { + err := doConfig(t, coreConfig, ` + mode = "spiffe" + ca_bundle_path = "blah" + `) + + spiretest.RequireGRPCStatusContains(t, err, codes.InvalidArgument, "you can not use ca_bundle_path or ca_bundle_paths in spiffe mode") + }) } func (s *Suite) loadPlugin(t *testing.T, config string) nodeattestor.NodeAttestor { @@ -307,6 +325,13 @@ ca_bundle_paths = %s return "" } +func (s *Suite) createConfigurationModeSPIFFE(extraConfig string) string { + return fmt.Sprintf(` +mode = "spiffe" +%s +`, extraConfig) +} + func marshal(t *testing.T, obj any) []byte { data, err := json.Marshal(obj) require.NoError(t, err) From b2c3cdf18829826ddbf367ab8d7362b5ecd17b14 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 13 Dec 2024 05:53:00 -0800 Subject: [PATCH 24/29] Add some new test certs Signed-off-by: Kevin Fox --- test/fixture/nodeattestor/x509pop/generate.go | 21 +++++++++++++++ .../nodeattestor/x509pop/intermediate.pem | 12 ++++----- .../nodeattestor/x509pop/leaf-crt-bundle.pem | 26 +++++++++---------- .../fixture/nodeattestor/x509pop/leaf-key.pem | 22 ++++++++-------- test/fixture/nodeattestor/x509pop/leaf.pem | 14 +++++----- .../fixture/nodeattestor/x509pop/root-crt.pem | 12 ++++----- .../nodeattestor/x509pop/svidexchange.pem | 22 ++++++++++++++++ test/fixture/nodeattestor/x509pop/svidreg.pem | 21 +++++++++++++++ 8 files changed, 107 insertions(+), 43 deletions(-) create mode 100644 test/fixture/nodeattestor/x509pop/svidexchange.pem create mode 100644 test/fixture/nodeattestor/x509pop/svidreg.pem diff --git a/test/fixture/nodeattestor/x509pop/generate.go b/test/fixture/nodeattestor/x509pop/generate.go index 9f89437975..6b4d80a38f 100644 --- a/test/fixture/nodeattestor/x509pop/generate.go +++ b/test/fixture/nodeattestor/x509pop/generate.go @@ -8,6 +8,7 @@ import ( "crypto/x509/pkix" "encoding/pem" "math/big" + "net/url" "os" "time" ) @@ -49,11 +50,31 @@ func main() { Subject: pkix.Name{CommonName: "COMMONNAME"}, }, intermediateKey, intermediateCert) + svid, _ := url.Parse("spiffe://example.org/somesvid") + spiffeLeafCertReg := createCertificate(leafKey, &x509.Certificate{ + SerialNumber: big.NewInt(0x0a1b2c3d4e6f), + KeyUsage: x509.KeyUsageDigitalSignature, + NotAfter: neverExpires, + Subject: pkix.Name{CommonName: "COMMONNAME"}, + URIs: []*url.URL{svid}, + }, intermediateKey, intermediateCert) + + svidExchange, _ := url.Parse("spiffe://example.org/spire-exchange/testhost") + spiffeLeafCertExchange := createCertificate(leafKey, &x509.Certificate{ + SerialNumber: big.NewInt(0x0a1b2c3d4e7f), + KeyUsage: x509.KeyUsageDigitalSignature, + NotAfter: neverExpires, + Subject: pkix.Name{CommonName: "COMMONNAME"}, + URIs: []*url.URL{svidExchange}, + }, intermediateKey, intermediateCert) + writeKey("leaf-key.pem", leafKey) writeCerts("leaf-crt-bundle.pem", leafCert, intermediateCert) writeCerts("leaf.pem", leafCert) writeCerts("intermediate.pem", intermediateCert) writeCerts("root-crt.pem", rootCert) + writeCerts("svidreg.pem", spiffeLeafCertReg, intermediateCert) + writeCerts("svidexchange.pem", spiffeLeafCertExchange, intermediateCert) } func createRootCertificate(key *rsa.PrivateKey, tmpl *x509.Certificate) *x509.Certificate { diff --git a/test/fixture/nodeattestor/x509pop/intermediate.pem b/test/fixture/nodeattestor/x509pop/intermediate.pem index d39455a0e8..2cda1da029 100644 --- a/test/fixture/nodeattestor/x509pop/intermediate.pem +++ b/test/fixture/nodeattestor/x509pop/intermediate.pem @@ -1,10 +1,10 @@ -----BEGIN CERTIFICATE----- MIIBaDCB86ADAgECAgNNXm8wDQYJKoZIhvcNAQELBQAwADAiGA8wMDAxMDEwMTAw MDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAAMHwwDQYJKoZIhvcNAQEBBQADawAwaAJh -AMq9BNinPfs3XWJzJ1qmxB93CaXA+sMD/6A+cXHfTkxwDcL/CvSsJckwkeDOM1qk -3w+1Jqap/MrGn73o4UIbAGFP+pAExNkZzMSebTEIntHeel7NkOI5G7rGLV8MzTcj -awIDAQABozIwMDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSndp4TSJoEojHy -FmZ0eFSq0PbubDANBgkqhkiG9w0BAQsFAANhAJxFRW6nM2qH0N/NhjzU0ijkDOIW -1VgsBtCV8YZSA2dWJ3mEyyB9PtYdfNL2269pDk5tsFyUr3VZSpmB7DclThhEwcnR -6SEUoqpy7HkeAK8OOQIr3OF/RymPye+Evg/xeQ== +AKpxtqJyka4hQtdKksZdUeSc5yNlu5L/bswWfq1QWR/v4SeWEjdxQVZ7KUiJ9/XB +traFmbMv880uI9F5F4zfeo0sdQ5aRNlWJWQXcAdoAxg5BDREYSz/HmZDTd9WXU4V +EwIDAQABozIwMDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRWojkh3HDe3KBD +qzktBMpG0G+ryzANBgkqhkiG9w0BAQsFAANhAIBVt7ACIz9e8tbo0Zac+qnFO2EM +oPm76JArWRM51uQFXDu6xDmJ1vqUckyq2yqeQEvyUe7TptqVEx+zZrP/40UFFkhF +keKJw5C1aZJvTvhVPFKmmQRiuDtRH4LzPt/HuQ== -----END CERTIFICATE----- diff --git a/test/fixture/nodeattestor/x509pop/leaf-crt-bundle.pem b/test/fixture/nodeattestor/x509pop/leaf-crt-bundle.pem index a03f95d573..3571b2e7a3 100644 --- a/test/fixture/nodeattestor/x509pop/leaf-crt-bundle.pem +++ b/test/fixture/nodeattestor/x509pop/leaf-crt-bundle.pem @@ -1,21 +1,21 @@ -----BEGIN CERTIFICATE----- MIIBgjCCAQygAwIBAgIGChssPU5fMA0GCSqGSIb3DQEBCwUAMAAwIhgPMDAwMTAx MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowFTETMBEGA1UEAxMKQ09NTU9OTkFN -RTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQDgxdLNdlVhNRNEcR5n9GdrNDXFV7CU -uE/XgXM5na9pOxeNSWTZ/StahYJONcNyLw8ZbiUREuHyx5lkNtFF5JcW3o1RoNBD -HFviyuJf7WUgDTBTctqQp/Y2q4uQPle92N8CAwEAAaMzMDEwDgYDVR0PAQH/BAQD -AgeAMB8GA1UdIwQYMBaAFKd2nhNImgSiMfIWZnR4VKrQ9u5sMA0GCSqGSIb3DQEB -CwUAA2EAr1WNJKb8H1pkPrMzzRLerF0QAK+/wal7OacRQ4x9pxl+rA+0bwgfsA0x -jSR/u6Jx83ASGMmuNLCnizqcNQtcj2hr1yPr5cPHFxjuISBPirNrkCONymnbhDU4 -RYoeKuK/ +RTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQCwkMtBkQNZtf9oFP/0WMzMHRSp0404 +UFSXkUL0YVNHRv/HnMyGdlA0cCWVsqmkktpEFNC/GYwG9YDDJuttWPMwiIiZN4B+ +ZuksNkKgwENGhRd/Y8w43U6gzJrwGJhfMzUCAwEAAaMzMDEwDgYDVR0PAQH/BAQD +AgeAMB8GA1UdIwQYMBaAFFaiOSHccN7coEOrOS0EykbQb6vLMA0GCSqGSIb3DQEB +CwUAA2EAC1/obuB4EjN66H7bOlOqgKzRN1v7YAC9hqKVxDs/CXs4DjAgsBrhiYIf +uSsNrHhIswS51r2iIMeYh9FTzxg6Nre7m5KoRgiI4pvcKBKQs5R/sF1Ywq1p7dH8 +q+vGqujB -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIBaDCB86ADAgECAgNNXm8wDQYJKoZIhvcNAQELBQAwADAiGA8wMDAxMDEwMTAw MDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAAMHwwDQYJKoZIhvcNAQEBBQADawAwaAJh -AMq9BNinPfs3XWJzJ1qmxB93CaXA+sMD/6A+cXHfTkxwDcL/CvSsJckwkeDOM1qk -3w+1Jqap/MrGn73o4UIbAGFP+pAExNkZzMSebTEIntHeel7NkOI5G7rGLV8MzTcj -awIDAQABozIwMDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSndp4TSJoEojHy -FmZ0eFSq0PbubDANBgkqhkiG9w0BAQsFAANhAJxFRW6nM2qH0N/NhjzU0ijkDOIW -1VgsBtCV8YZSA2dWJ3mEyyB9PtYdfNL2269pDk5tsFyUr3VZSpmB7DclThhEwcnR -6SEUoqpy7HkeAK8OOQIr3OF/RymPye+Evg/xeQ== +AKpxtqJyka4hQtdKksZdUeSc5yNlu5L/bswWfq1QWR/v4SeWEjdxQVZ7KUiJ9/XB +traFmbMv880uI9F5F4zfeo0sdQ5aRNlWJWQXcAdoAxg5BDREYSz/HmZDTd9WXU4V +EwIDAQABozIwMDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRWojkh3HDe3KBD +qzktBMpG0G+ryzANBgkqhkiG9w0BAQsFAANhAIBVt7ACIz9e8tbo0Zac+qnFO2EM +oPm76JArWRM51uQFXDu6xDmJ1vqUckyq2yqeQEvyUe7TptqVEx+zZrP/40UFFkhF +keKJw5C1aZJvTvhVPFKmmQRiuDtRH4LzPt/HuQ== -----END CERTIFICATE----- diff --git a/test/fixture/nodeattestor/x509pop/leaf-key.pem b/test/fixture/nodeattestor/x509pop/leaf-key.pem index 55b38ea94d..5098c5c623 100644 --- a/test/fixture/nodeattestor/x509pop/leaf-key.pem +++ b/test/fixture/nodeattestor/x509pop/leaf-key.pem @@ -1,13 +1,13 @@ -----BEGIN PRIVATE KEY----- -MIIB5QIBADANBgkqhkiG9w0BAQEFAASCAc8wggHLAgEAAmEA4MXSzXZVYTUTRHEe -Z/RnazQ1xVewlLhP14FzOZ2vaTsXjUlk2f0rWoWCTjXDci8PGW4lERLh8seZZDbR -ReSXFt6NUaDQQxxb4sriX+1lIA0wU3LakKf2NquLkD5XvdjfAgMBAAECYQDQw0ug -Iwj3qPkFN177REeNjUdny6Mo7erSCNxBJhCWVxjv07wErxxGDfxRsaOeeM76uIdK -GDDC24ehhw67EKXG+ZR6yjOtCmBoSSg0irCa02bVolEZKGd/oNuDv2O7u/kCMQDs -rPvXsTSs3wSaow+HxiCg0q79LEj7n56RD5f36cOh+9FFR6lTnq90YNCkenPmep0C -MQDzIArz5XKqa6wngoDz13l4Rh7WELLe8L/H8uFW5a9sh6N2NtbVcTTKtdiMRiLO -GqsCMER6YHut6AHEnT5ow4uTRcmCa+MQtyUmr+hkzKGMX3A0vf3/l/x7w2Q2xudw -Dqy2+QIxAL1M8bo9qS5wb7bDskwqNfYkAtaCYgOcSHcS8cmLRS+n3OqqgFPotUYw -mM9/vJHlyQIwAoq+1Ydsmb3VBwhjVOCVPtNgACcfTSZkv/wQQUUGsLZxMxjTLKeG -EvB1EDwioRiE +MIIB5gIBADANBgkqhkiG9w0BAQEFAASCAdAwggHMAgEAAmEAsJDLQZEDWbX/aBT/ +9FjMzB0UqdONOFBUl5FC9GFTR0b/x5zMhnZQNHAllbKppJLaRBTQvxmMBvWAwybr +bVjzMIiImTeAfmbpLDZCoMBDRoUXf2PMON1OoMya8BiYXzM1AgMBAAECYQCQ393H +CMOlAo50ynZR+eLgwCPKTQkc4dznGIvFlW4NmBYbpW60DbQ1sqdEM3q6zLrqJypr +7ganReJLIrVl3KDsiwTNey50L7xbLQYNMKRsVJ+yVK0vBzyhEiSgOqhe9SkCMQDT +ZQS8IkrhRijtqkh3Z6+S2PBSCTIL8fq9urjnXvrUEYJyMz1NXmU3eE64It8ZGEMC +MQDV0md67EYLZNwb4f+DzPSfFfimi+4TQIhs23z0fDEujMaJ8iX1SovL18Ka48+t +6ycCMQCMJ47DGU1iHH0oTdzr5b+/cbur+FLJHq8qubC8HfnZPp6pDpXXRP2AkHBI +nz4hSjcCMBFWaDGdauiNmxNftdo4CjXEEE9g1UMWXnmFKpKgZ1SA8bBJxC4ph0BW +FF9+zV4qzQIxAMm0CyNcLy30XT00Q2wTu8UCI31xEOkWdsacSxy81ZH5pTCyS6bT +OuvwN3YFTG4guw== -----END PRIVATE KEY----- diff --git a/test/fixture/nodeattestor/x509pop/leaf.pem b/test/fixture/nodeattestor/x509pop/leaf.pem index cd36504f20..1aa5d84c70 100644 --- a/test/fixture/nodeattestor/x509pop/leaf.pem +++ b/test/fixture/nodeattestor/x509pop/leaf.pem @@ -1,11 +1,11 @@ -----BEGIN CERTIFICATE----- MIIBgjCCAQygAwIBAgIGChssPU5fMA0GCSqGSIb3DQEBCwUAMAAwIhgPMDAwMTAx MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowFTETMBEGA1UEAxMKQ09NTU9OTkFN -RTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQDgxdLNdlVhNRNEcR5n9GdrNDXFV7CU -uE/XgXM5na9pOxeNSWTZ/StahYJONcNyLw8ZbiUREuHyx5lkNtFF5JcW3o1RoNBD -HFviyuJf7WUgDTBTctqQp/Y2q4uQPle92N8CAwEAAaMzMDEwDgYDVR0PAQH/BAQD -AgeAMB8GA1UdIwQYMBaAFKd2nhNImgSiMfIWZnR4VKrQ9u5sMA0GCSqGSIb3DQEB -CwUAA2EAr1WNJKb8H1pkPrMzzRLerF0QAK+/wal7OacRQ4x9pxl+rA+0bwgfsA0x -jSR/u6Jx83ASGMmuNLCnizqcNQtcj2hr1yPr5cPHFxjuISBPirNrkCONymnbhDU4 -RYoeKuK/ +RTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQCwkMtBkQNZtf9oFP/0WMzMHRSp0404 +UFSXkUL0YVNHRv/HnMyGdlA0cCWVsqmkktpEFNC/GYwG9YDDJuttWPMwiIiZN4B+ +ZuksNkKgwENGhRd/Y8w43U6gzJrwGJhfMzUCAwEAAaMzMDEwDgYDVR0PAQH/BAQD +AgeAMB8GA1UdIwQYMBaAFFaiOSHccN7coEOrOS0EykbQb6vLMA0GCSqGSIb3DQEB +CwUAA2EAC1/obuB4EjN66H7bOlOqgKzRN1v7YAC9hqKVxDs/CXs4DjAgsBrhiYIf +uSsNrHhIswS51r2iIMeYh9FTzxg6Nre7m5KoRgiI4pvcKBKQs5R/sF1Ywq1p7dH8 +q+vGqujB -----END CERTIFICATE----- diff --git a/test/fixture/nodeattestor/x509pop/root-crt.pem b/test/fixture/nodeattestor/x509pop/root-crt.pem index ca8510b2f8..be223d0c35 100644 --- a/test/fixture/nodeattestor/x509pop/root-crt.pem +++ b/test/fixture/nodeattestor/x509pop/root-crt.pem @@ -1,10 +1,10 @@ -----BEGIN CERTIFICATE----- MIIBaDCB86ADAgECAgMaKzwwDQYJKoZIhvcNAQELBQAwADAiGA8wMDAxMDEwMTAw MDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAAMHwwDQYJKoZIhvcNAQEBBQADawAwaAJh -AL4/Jxt3mhua02ZqRhineH5BFYCaDaBWNs8p3twsXEr0E6b9onZpUnkgdIVseU0X -qjuZP3c4/i2c1TnwN5iomXfj3hzeTB5e/D+kzX6+UxvFrMayxtGKNbCHr+ZIjU18 -5QIDAQABozIwMDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQEvQNv9Uxp79n0 -yMXzvMBRSoDX8DANBgkqhkiG9w0BAQsFAANhAAvYkMYqEnl8XLq2luwy6XJyXuLI -Ge+TFgoBIYsLryfLua3l/tMhm2l7497PMXJDVwSwObTRKbkVmwzFGEHcqb/x7kyJ -QJJJHT6TOAWuqv6HbdCJI/jJzRn3zm3ngyDqIg== +AJTE1G18AwWWAn0/UNj1uJWR3spmfzk+Z/aKWAadZWpB7atofJN5nITymAmTShG5 +ZBfoo028+aG818DQaTN4mw6Bt47WeXLVsuS9hvy9VuyQSNS19Egi5Vzo5h70amK/ +KQIDAQABozIwMDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQL3ZehHKEWw1WU +yXpDDxKTX96WUjANBgkqhkiG9w0BAQsFAANhABicwitW0/1S/IBU4uHxxWh9k+8+ +lQc893kblAL0SsqlZL/qxpVWS271T/gQc2ShvXJf7JabRRQ3jTtos33L5FqpJD5q +PVv3RRQ3Ex/+SMNgi+NOAqNL/GP+8Y3SxQWhlg== -----END CERTIFICATE----- diff --git a/test/fixture/nodeattestor/x509pop/svidexchange.pem b/test/fixture/nodeattestor/x509pop/svidexchange.pem new file mode 100644 index 0000000000..870a82d069 --- /dev/null +++ b/test/fixture/nodeattestor/x509pop/svidexchange.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIBuzCCAUWgAwIBAgIGChssPU5/MA0GCSqGSIb3DQEBCwUAMAAwIhgPMDAwMTAx +MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowFTETMBEGA1UEAxMKQ09NTU9OTkFN +RTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQCwkMtBkQNZtf9oFP/0WMzMHRSp0404 +UFSXkUL0YVNHRv/HnMyGdlA0cCWVsqmkktpEFNC/GYwG9YDDJuttWPMwiIiZN4B+ +ZuksNkKgwENGhRd/Y8w43U6gzJrwGJhfMzUCAwEAAaNsMGowDgYDVR0PAQH/BAQD +AgeAMB8GA1UdIwQYMBaAFFaiOSHccN7coEOrOS0EykbQb6vLMDcGA1UdEQQwMC6G +LHNwaWZmZTovL2V4YW1wbGUub3JnL3NwaXJlLWV4Y2hhbmdlL3Rlc3Rob3N0MA0G +CSqGSIb3DQEBCwUAA2EAe/b53CFjgrJRAN7LoFksxpEqtty7Z7sgZkf/D2gsWaAx +QoX7HHgU2Agso4ivnksuJGjsrjFXbG+qmvMeLG6mjst0mHrlm9e6QNtpN5k/mj6K +gX3hcDmO2sXXrhU8ySYt +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBaDCB86ADAgECAgNNXm8wDQYJKoZIhvcNAQELBQAwADAiGA8wMDAxMDEwMTAw +MDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAAMHwwDQYJKoZIhvcNAQEBBQADawAwaAJh +AKpxtqJyka4hQtdKksZdUeSc5yNlu5L/bswWfq1QWR/v4SeWEjdxQVZ7KUiJ9/XB +traFmbMv880uI9F5F4zfeo0sdQ5aRNlWJWQXcAdoAxg5BDREYSz/HmZDTd9WXU4V +EwIDAQABozIwMDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRWojkh3HDe3KBD +qzktBMpG0G+ryzANBgkqhkiG9w0BAQsFAANhAIBVt7ACIz9e8tbo0Zac+qnFO2EM +oPm76JArWRM51uQFXDu6xDmJ1vqUckyq2yqeQEvyUe7TptqVEx+zZrP/40UFFkhF +keKJw5C1aZJvTvhVPFKmmQRiuDtRH4LzPt/HuQ== +-----END CERTIFICATE----- diff --git a/test/fixture/nodeattestor/x509pop/svidreg.pem b/test/fixture/nodeattestor/x509pop/svidreg.pem new file mode 100644 index 0000000000..405a5fbae6 --- /dev/null +++ b/test/fixture/nodeattestor/x509pop/svidreg.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIBrDCCATagAwIBAgIGChssPU5vMA0GCSqGSIb3DQEBCwUAMAAwIhgPMDAwMTAx +MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowFTETMBEGA1UEAxMKQ09NTU9OTkFN +RTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQCwkMtBkQNZtf9oFP/0WMzMHRSp0404 +UFSXkUL0YVNHRv/HnMyGdlA0cCWVsqmkktpEFNC/GYwG9YDDJuttWPMwiIiZN4B+ +ZuksNkKgwENGhRd/Y8w43U6gzJrwGJhfMzUCAwEAAaNdMFswDgYDVR0PAQH/BAQD +AgeAMB8GA1UdIwQYMBaAFFaiOSHccN7coEOrOS0EykbQb6vLMCgGA1UdEQQhMB+G +HXNwaWZmZTovL2V4YW1wbGUub3JnL3NvbWVzdmlkMA0GCSqGSIb3DQEBCwUAA2EA +K6U+E2Kq7bR4z8Cc1iaO3mgC2SUAieLDkf8tKcKWKPkd+QMiVR4wAAyvGdRyQynI +taMknAJxe84hy43OF6JRP9Pz1/c37KFzsGrulC1gjBODeIw+JfkWafy2l43HyLrf +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBaDCB86ADAgECAgNNXm8wDQYJKoZIhvcNAQELBQAwADAiGA8wMDAxMDEwMTAw +MDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAAMHwwDQYJKoZIhvcNAQEBBQADawAwaAJh +AKpxtqJyka4hQtdKksZdUeSc5yNlu5L/bswWfq1QWR/v4SeWEjdxQVZ7KUiJ9/XB +traFmbMv880uI9F5F4zfeo0sdQ5aRNlWJWQXcAdoAxg5BDREYSz/HmZDTd9WXU4V +EwIDAQABozIwMDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRWojkh3HDe3KBD +qzktBMpG0G+ryzANBgkqhkiG9w0BAQsFAANhAIBVt7ACIz9e8tbo0Zac+qnFO2EM +oPm76JArWRM51uQFXDu6xDmJ1vqUckyq2yqeQEvyUe7TptqVEx+zZrP/40UFFkhF +keKJw5C1aZJvTvhVPFKmmQRiuDtRH4LzPt/HuQ== +-----END CERTIFICATE----- From 78e61ebaa168bca3fa15d589f31821ab517e16e0 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 13 Dec 2024 08:01:55 -0800 Subject: [PATCH 25/29] Initial x509 spiffe test Signed-off-by: Kevin Fox --- .../nodeattestor/x509pop/x509pop_test.go | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go index 96f49a8fc2..8b76d1baf5 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go @@ -6,12 +6,15 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" + "encoding/pem" "errors" "fmt" + "os" "testing" "github.com/spiffe/go-spiffe/v2/spiffeid" identityproviderv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/hostservice/server/identityprovider/v1" + plugintypes "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/types" "github.com/spiffe/spire/pkg/common/catalog" "github.com/spiffe/spire/pkg/common/plugin/x509pop" "github.com/spiffe/spire/pkg/server/plugin/nodeattestor" @@ -38,6 +41,8 @@ type Suite struct { leafCert *x509.Certificate intermediateCert *x509.Certificate rootCert *x509.Certificate + svidReg [][]byte + svidExchange [][]byte alternativeBundlePath string alternativeBundle *x509.Certificate @@ -49,6 +54,8 @@ func (s *Suite) SetupTest() { s.rootCertPath = fixture.Join("nodeattestor", "x509pop", "root-crt.pem") leafCertPath := fixture.Join("nodeattestor", "x509pop", "leaf-crt-bundle.pem") leafKeyPath := fixture.Join("nodeattestor", "x509pop", "leaf-key.pem") + svidRegPath := fixture.Join("nodeattestor", "x509pop", "svidreg.pem") + svidExchangePath := fixture.Join("nodeattestor", "x509pop", "svidexchange.pem") kp, err := tls.LoadX509KeyPair(leafCertPath, leafKeyPath) require.NoError(err) @@ -61,6 +68,15 @@ func (s *Suite) SetupTest() { s.rootCert, err = util.LoadCert(s.rootCertPath) require.NoError(err) + kp, err = tls.LoadX509KeyPair(svidRegPath, leafKeyPath) + require.NoError(err) + s.svidReg = kp.Certificate + require.NoError(err) + + kp, err = tls.LoadX509KeyPair(svidExchangePath, leafKeyPath) + require.NoError(err) + s.svidExchange = kp.Certificate + // Add alternative bundle s.alternativeBundlePath = fixture.Join("certs", "ca.pem") s.alternativeBundle, err = util.LoadCert(s.alternativeBundlePath) @@ -72,26 +88,43 @@ func (s *Suite) TestAttestSuccess() { desc string giveConfig string expectAgentID string + certs [][]byte + serialnumber string }{ { desc: "default success (ca_bundle_path)", expectAgentID: "spiffe://example.org/spire/agent/x509pop/" + x509pop.Fingerprint(s.leafCert), giveConfig: s.createConfiguration("ca_bundle_path", ""), + certs: s.leafBundle, + serialnumber: "serialnumber:0a1b2c3d4e5f", }, { desc: "success with custom agent id (ca_bundle_path)", expectAgentID: "spiffe://example.org/spire/agent/cn/COMMONNAME", giveConfig: s.createConfiguration("ca_bundle_path", `agent_path_template = "/cn/{{ .Subject.CommonName }}"`), + certs: s.leafBundle, + serialnumber: "serialnumber:0a1b2c3d4e5f", }, { desc: "default success (ca_bundle_paths)", expectAgentID: "spiffe://example.org/spire/agent/x509pop/" + x509pop.Fingerprint(s.leafCert), giveConfig: s.createConfiguration("ca_bundle_path", ""), + certs: s.leafBundle, + serialnumber: "serialnumber:0a1b2c3d4e5f", }, { desc: "success with custom agent id (ca_bundle_paths)", expectAgentID: "spiffe://example.org/spire/agent/serialnumber/0a1b2c3d4e5f", giveConfig: s.createConfiguration("ca_bundle_paths", `agent_path_template = "/serialnumber/{{ .SerialNumberHex }}"`), + certs: s.leafBundle, + serialnumber: "serialnumber:0a1b2c3d4e5f", + }, + { + desc: "success with spiffe exchange", + expectAgentID: "spiffe://example.org/spire/agent/x509pop/testhost", + giveConfig: s.createConfigurationModeSPIFFE(""), + certs: s.svidExchange, + serialnumber: "serialnumber:0a1b2c3d4e7f", }, } @@ -101,7 +134,7 @@ func (s *Suite) TestAttestSuccess() { attestor := s.loadPlugin(t, tt.giveConfig) attestationData := &x509pop.AttestationData{ - Certificates: s.leafBundle, + Certificates: tt.certs, } payload := marshal(t, attestationData) @@ -126,7 +159,7 @@ func (s *Suite) TestAttestSuccess() { {Type: "x509pop", Value: "subject:cn:COMMONNAME"}, {Type: "x509pop", Value: "ca:fingerprint:" + x509pop.Fingerprint(s.intermediateCert)}, {Type: "x509pop", Value: "ca:fingerprint:" + x509pop.Fingerprint(s.rootCert)}, - {Type: "x509pop", Value: "serialnumber:0a1b2c3d4e5f"}, + {Type: "x509pop", Value: tt.serialnumber}, }, result.Selectors) }) } @@ -293,8 +326,34 @@ func (s *Suite) TestConfigure() { func (s *Suite) loadPlugin(t *testing.T, config string) nodeattestor.NodeAttestor { v1 := new(nodeattestor.V1) + + caRaw, err := os.ReadFile(s.rootCertPath) + if err != nil { + return nil + } + ca, _ := pem.Decode([]byte(caRaw)) +/* der, err := x509.MarshalPKIXPublicKey(ca) + if der == nil { + return nil + }*/ + + bundle := &plugintypes.Bundle{ + X509Authorities: []*plugintypes.X509Certificate{ + {Asn1: ca.Bytes}, + // []byte("FOO")}, + }, + } + + +// td := spiffeid.RequireTrustDomainFromString("example.org") +// bundle, err := x509bundle.Load(td, s.rootCertPath) +// if err != nil { +// return nil +// } + identityProvider := fakeidentityprovider.New() + identityProvider.AppendBundle(bundle) plugintest.Load(t, BuiltIn(), v1, - plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(fakeidentityprovider.New())), + plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider)), plugintest.CoreConfig(catalog.CoreConfig{ TrustDomain: spiffeid.RequireTrustDomainFromString("example.org"), }), From 1c75f363658683074c4f42c23a078ce9fa68ab07 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 13 Dec 2024 08:50:10 -0800 Subject: [PATCH 26/29] Add some failure testing too Signed-off-by: Kevin Fox --- .../nodeattestor/x509pop/x509pop_test.go | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go index 8b76d1baf5..5457d6ae1b 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go @@ -167,6 +167,7 @@ func (s *Suite) TestAttestSuccess() { func (s *Suite) TestAttestFailure() { successConfiguration := s.createConfiguration("ca_bundle_path", "") + spiffeConfiguration := s.createConfigurationModeSPIFFE("") makePayload := func(t *testing.T, attestationData *x509pop.AttestationData) []byte { return marshal(t, attestationData) @@ -179,13 +180,24 @@ func (s *Suite) TestAttestFailure() { require.Nil(t, result) } - challengeResponseFails := func(t *testing.T, attestor nodeattestor.NodeAttestor, challengeResp string, expectCode codes.Code, expectMessage string) { + + challengeResponseFails := func(t *testing.T, attestor nodeattestor.NodeAttestor, certs [][]byte, challengeResp string, fullChallenge bool, expectCode codes.Code, expectMessage string) { payload := makePayload(t, &x509pop.AttestationData{ - Certificates: s.leafBundle, + Certificates: certs, }) doChallenge := func(ctx context.Context, challenge []byte) ([]byte, error) { return []byte(challengeResp), nil } + if fullChallenge { + doChallenge = func(ctx context.Context, challenge []byte) ([]byte, error) { + require.NotEmpty(t, challenge) + popChallenge := new(x509pop.Challenge) + unmarshal(t, challenge, popChallenge) + response, err := x509pop.CalculateResponse(s.leafKey, popChallenge) + require.NoError(t, err) + return marshal(t, response), nil + } + } result, err := attestor.Attest(context.Background(), payload, doChallenge) spiretest.RequireGRPCStatusContains(t, err, expectCode, expectMessage) require.Nil(t, result) @@ -240,12 +252,24 @@ func (s *Suite) TestAttestFailure() { s.T().Run("malformed challenge response", func(t *testing.T) { attestor := s.loadPlugin(t, successConfiguration) - challengeResponseFails(t, attestor, "", codes.InvalidArgument, "nodeattestor(x509pop): unable to unmarshal challenge response") + challengeResponseFails(t, attestor, s.leafBundle, "", false, codes.InvalidArgument, "nodeattestor(x509pop): unable to unmarshal challenge response") }) s.T().Run("invalid response", func(t *testing.T) { attestor := s.loadPlugin(t, successConfiguration) - challengeResponseFails(t, attestor, "{}", codes.PermissionDenied, "nodeattestor(x509pop): challenge response verification failed") + challengeResponseFails(t, attestor, s.leafBundle, "{}", false, codes.PermissionDenied, "nodeattestor(x509pop): challenge response verification failed") + }) + + s.T().Run("spiffe bad prefix", func(t *testing.T) { + attestor := s.loadPlugin(t, spiffeConfiguration) + + challengeResponseFails(t, attestor, s.svidReg, "", true, codes.PermissionDenied, "nodeattestor(x509pop): x509 cert doesnt match SVID prefix") + }) + + s.T().Run("spiffe non svid", func(t *testing.T) { + attestor := s.loadPlugin(t, spiffeConfiguration) + + challengeResponseFails(t, attestor, s.leafBundle, "", true, codes.PermissionDenied, "nodeattestor(x509pop): valid SVID x509 cert not found") }) } @@ -332,24 +356,13 @@ func (s *Suite) loadPlugin(t *testing.T, config string) nodeattestor.NodeAttesto return nil } ca, _ := pem.Decode([]byte(caRaw)) -/* der, err := x509.MarshalPKIXPublicKey(ca) - if der == nil { - return nil - }*/ bundle := &plugintypes.Bundle{ X509Authorities: []*plugintypes.X509Certificate{ {Asn1: ca.Bytes}, - // []byte("FOO")}, }, } - -// td := spiffeid.RequireTrustDomainFromString("example.org") -// bundle, err := x509bundle.Load(td, s.rootCertPath) -// if err != nil { -// return nil -// } identityProvider := fakeidentityprovider.New() identityProvider.AppendBundle(bundle) plugintest.Load(t, BuiltIn(), v1, From 238edcbcb19fdfd9cacfdfab46a76bf415b482b2 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 13 Dec 2024 09:06:15 -0800 Subject: [PATCH 27/29] Add no bundle test. Cleanup Signed-off-by: Kevin Fox --- .../nodeattestor/x509pop/x509pop_test.go | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go index 5457d6ae1b..70eeef3969 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go @@ -271,6 +271,12 @@ func (s *Suite) TestAttestFailure() { challengeResponseFails(t, attestor, s.leafBundle, "", true, codes.PermissionDenied, "nodeattestor(x509pop): valid SVID x509 cert not found") }) + + s.T().Run("spiffe non svid", func(t *testing.T) { + attestor := s.loadPluginFull(t, spiffeConfiguration, fakeidentityprovider.New()) + + challengeResponseFails(t, attestor, s.svidExchange, "", true, codes.Internal, "nodeattestor(x509pop): failed to get trust bundle") + }) } func (s *Suite) TestConfigure() { @@ -349,22 +355,28 @@ func (s *Suite) TestConfigure() { } func (s *Suite) loadPlugin(t *testing.T, config string) nodeattestor.NodeAttestor { + return s.loadPluginFull(t, config, nil) +} + +func (s *Suite) loadPluginFull(t *testing.T, config string, identityProvider *fakeidentityprovider.IdentityProvider) nodeattestor.NodeAttestor { v1 := new(nodeattestor.V1) - caRaw, err := os.ReadFile(s.rootCertPath) - if err != nil { - return nil - } - ca, _ := pem.Decode([]byte(caRaw)) + if identityProvider == nil { + caRaw, err := os.ReadFile(s.rootCertPath) + if err != nil { + return nil + } + ca, _ := pem.Decode([]byte(caRaw)) - bundle := &plugintypes.Bundle{ - X509Authorities: []*plugintypes.X509Certificate{ - {Asn1: ca.Bytes}, - }, - } + bundle := &plugintypes.Bundle{ + X509Authorities: []*plugintypes.X509Certificate{ + {Asn1: ca.Bytes}, + }, + } - identityProvider := fakeidentityprovider.New() - identityProvider.AppendBundle(bundle) + identityProvider = fakeidentityprovider.New() + identityProvider.AppendBundle(bundle) + } plugintest.Load(t, BuiltIn(), v1, plugintest.HostServices(identityproviderv1.IdentityProviderServiceServer(identityProvider)), plugintest.CoreConfig(catalog.CoreConfig{ From 2b5c8844d6f9d36d9883775c68331a875c0a2b65 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 13 Dec 2024 09:18:19 -0800 Subject: [PATCH 28/29] Fix lint Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop.go | 6 +++--- pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go index f824ef4b85..4ee2809be3 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop.go @@ -107,7 +107,7 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco pathTemplate = tmpl } - svidPrefix := "/spire-exchange/" + svidPrefix := "/spire-exchange/" if hclConfig.SVIDPrefix != nil { svidPrefix = *hclConfig.SVIDPrefix if !strings.HasSuffix(svidPrefix, "/") { @@ -291,7 +291,7 @@ func (p *Plugin) Validate(_ context.Context, req *configv1.ValidateRequest) (*co // SetLogger sets this plugin's logger func (p *Plugin) SetLogger(log hclog.Logger) { - p.log = log + p.log = log } func (p *Plugin) getTrustBundle(ctx context.Context) (*x509.CertPool, error) { @@ -310,7 +310,7 @@ func (p *Plugin) getTrustBundle(ctx context.Context) (*x509.CertPool, error) { if len(trustBundles) > 0 { return util.NewCertPool(trustBundles...), nil } - p.log.Warn("No trust bundle retrieved from SPIRE") + p.log.Warn("No trust bundle retrieved from SPIRE") return nil, nil } diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go index 70eeef3969..7b73149a64 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go @@ -180,7 +180,6 @@ func (s *Suite) TestAttestFailure() { require.Nil(t, result) } - challengeResponseFails := func(t *testing.T, attestor nodeattestor.NodeAttestor, certs [][]byte, challengeResp string, fullChallenge bool, expectCode codes.Code, expectMessage string) { payload := makePayload(t, &x509pop.AttestationData{ Certificates: certs, From 021a90b866f993ce66a0d7f6dcd725c2c7f54694 Mon Sep 17 00:00:00 2001 From: Kevin Fox Date: Fri, 13 Dec 2024 09:27:45 -0800 Subject: [PATCH 29/29] More lint Signed-off-by: Kevin Fox --- pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go index 7b73149a64..8c641b6987 100644 --- a/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go +++ b/pkg/server/plugin/nodeattestor/x509pop/x509pop_test.go @@ -365,7 +365,7 @@ func (s *Suite) loadPluginFull(t *testing.T, config string, identityProvider *fa if err != nil { return nil } - ca, _ := pem.Decode([]byte(caRaw)) + ca, _ := pem.Decode(caRaw) bundle := &plugintypes.Bundle{ X509Authorities: []*plugintypes.X509Certificate{