-
Notifications
You must be signed in to change notification settings - Fork 485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
x509pop server plugin support for servers trust bundle #5572
Changes from all commits
0a9b0d4
38e9850
b03cd79
483d2de
dd8f394
0c9aa54
040e70d
6ea18ad
01e9c3f
2cb1529
44e1cbf
6359989
a881a47
fe5bb17
487100d
4b06fab
d55f40d
c12860b
6ecc0b8
bf95562
cf1cb52
b7f77d0
289ae88
66c21d3
d34d912
4d94054
4c6ee68
21f2075
e476ccf
dbb120d
7a8b778
de1611a
331f003
bed2c85
941236b
bdf284a
037ac50
2f1166d
aab491a
378bdda
b7eea56
9f6b9f7
f1385d4
e4313e3
ee6e679
c599b12
7820d1a
fd6a320
b2c3cdf
78e61eb
40e94ac
1c75f36
238edcb
2b5c884
021a90b
50ebc2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,10 +4,14 @@ import ( | |
"context" | ||
"crypto/x509" | ||
"encoding/json" | ||
"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" | ||
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" | ||
"github.com/spiffe/spire/pkg/common/agentpathtemplate" | ||
|
@@ -35,12 +39,16 @@ func builtin(p *Plugin) catalog.BuiltIn { | |
} | ||
|
||
type Config struct { | ||
Mode string `hcl:"mode"` | ||
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 { | ||
mode string | ||
svidPrefix string | ||
trustDomain spiffeid.TrustDomain | ||
trustBundle *x509.CertPool | ||
pathTemplate *agentpathtemplate.Template | ||
|
@@ -53,29 +61,44 @@ 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.Mode == "" { | ||
hclConfig.Mode = "external_pki" | ||
} | ||
if hclConfig.CABundlePath != "" { | ||
caPaths = []string{hclConfig.CABundlePath} | ||
} else { | ||
caPaths = hclConfig.CABundlePaths | ||
if hclConfig.Mode != "external_pki" && hclConfig.Mode != "spiffe" { | ||
status.ReportError("mode can only be either spiffe or external_pki") | ||
} | ||
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.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") | ||
} | ||
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") | ||
} | ||
trustBundles = append(trustBundles, certs...) | ||
|
||
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...) | ||
} | ||
} | ||
|
||
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.DefaultAgentPathTemplate | ||
pathTemplate := x509pop.DefaultAgentPathTemplateCN | ||
if hclConfig.Mode == "spiffe" { | ||
pathTemplate = x509pop.DefaultAgentPathTemplateSVID | ||
} | ||
if len(hclConfig.AgentPathTemplate) > 0 { | ||
tmpl, err := agentpathtemplate.Parse(hclConfig.AgentPathTemplate) | ||
if err != nil { | ||
|
@@ -84,10 +107,20 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco | |
pathTemplate = tmpl | ||
} | ||
|
||
svidPrefix := "/spire-exchange/" | ||
if hclConfig.SVIDPrefix != nil { | ||
svidPrefix = *hclConfig.SVIDPrefix | ||
if !strings.HasSuffix(svidPrefix, "/") { | ||
svidPrefix += "/" | ||
} | ||
} | ||
|
||
newConfig := &configuration{ | ||
trustDomain: coreConfig.TrustDomain, | ||
trustBundle: util.NewCertPool(trustBundles...), | ||
pathTemplate: pathTemplate, | ||
mode: hclConfig.Mode, | ||
svidPrefix: svidPrefix, | ||
} | ||
|
||
return newConfig | ||
|
@@ -97,14 +130,24 @@ type Plugin struct { | |
nodeattestorv1.UnsafeNodeAttestorServer | ||
configv1.UnsafeConfigServer | ||
|
||
m sync.Mutex | ||
config *configuration | ||
log hclog.Logger | ||
|
||
m sync.Mutex | ||
config *configuration | ||
identityProvider identityproviderv1.IdentityProviderServiceClient | ||
} | ||
|
||
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") | ||
} | ||
return nil | ||
} | ||
|
||
func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { | ||
req, err := stream.Recv() | ||
if err != nil { | ||
|
@@ -143,10 +186,18 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { | |
intermediates.AddCert(intermediate) | ||
} | ||
|
||
trustBundle := config.trustBundle | ||
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) | ||
} | ||
} | ||
|
||
// verify the chain of trust | ||
chains, err := leaf.Verify(x509.VerifyOptions{ | ||
Intermediates: intermediates, | ||
Roots: config.trustBundle, | ||
Roots: trustBundle, | ||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, | ||
}) | ||
if err != nil { | ||
|
@@ -188,7 +239,19 @@ 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.mode == "spiffe" { | ||
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.svidPrefix) { | ||
return status.Errorf(codes.PermissionDenied, "x509 cert doesnt match SVID prefix") | ||
} | ||
svidPath = strings.TrimPrefix(svidPath, config.svidPrefix) | ||
} | ||
|
||
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) | ||
} | ||
|
@@ -226,6 +289,31 @@ 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 { | ||
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 | ||
} | ||
p.log.Warn("No trust bundle retrieved from SPIRE") | ||
return nil, nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if this happens we'll reject attestations, but it is going to be hard to understand why it is happening (basically we are not going to have a trusted bundle) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we log a warning about not currently finding a trust bundle? Its nor fatal as it might show up at some point later? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeap, I agree but this attestation will fail and mention a verification error There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a warning. |
||
} | ||
|
||
func (p *Plugin) getConfig() (*configuration, error) { | ||
p.m.Lock() | ||
defer p.m.Unlock() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is not the same error happens in line 80?
may we return here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.... The check here checks if both are configured. The check at 80 ensures at least one is configured. Both are needed I think.
I was thinking status.ReportError was fatal. Is it not? Maybe all the ReportError's here then need retruns?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmm, it is like if both are configured, you must never get the second one right?
status.Report is not a fatal, but just adding a list of errors, than then are used in configure or verify,
but this approach has a good win than you can get the complete list of errors to resolve all together,
but it makes very hard to follow and see dependency errors...
In any case we are fine for now