forked from spiffe/spire
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optimize spire server tls connection bundle conversion amount (spiffe…
…#3759) Signed-off-by: Guilherme Carvalho <[email protected]>
- Loading branch information
1 parent
2ed7d91
commit 2c29cd4
Showing
5 changed files
with
175 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package bundle | ||
|
||
import ( | ||
"context" | ||
"crypto/x509" | ||
"fmt" | ||
"sync" | ||
"time" | ||
|
||
"github.com/andres-erbsen/clock" | ||
"github.com/spiffe/go-spiffe/v2/spiffeid" | ||
"github.com/spiffe/spire/proto/spire/common" | ||
"google.golang.org/protobuf/proto" | ||
|
||
"github.com/spiffe/go-spiffe/v2/bundle/x509bundle" | ||
"github.com/spiffe/spire/pkg/server/datastore" | ||
) | ||
|
||
const ( | ||
cacheExpiry = time.Second | ||
) | ||
|
||
type Cache struct { | ||
ds datastore.DataStore | ||
bundlesMtx sync.Mutex | ||
bundles map[spiffeid.TrustDomain]*bundleEntry | ||
clock clock.Clock | ||
} | ||
|
||
func NewCache(ds datastore.DataStore, clk clock.Clock) *Cache { | ||
return &Cache{ | ||
ds: ds, | ||
clock: clk, | ||
bundles: make(map[spiffeid.TrustDomain]*bundleEntry), | ||
} | ||
} | ||
|
||
type bundleEntry struct { | ||
mu sync.Mutex | ||
ts time.Time | ||
bundle *common.Bundle | ||
x509Bundle *x509bundle.Bundle | ||
} | ||
|
||
func (c *Cache) FetchBundleX509(ctx context.Context, td spiffeid.TrustDomain) (*x509bundle.Bundle, error) { | ||
c.bundlesMtx.Lock() | ||
entry, ok := c.bundles[td] | ||
if !ok { | ||
entry = &bundleEntry{} | ||
c.bundles[td] = entry | ||
} | ||
c.bundlesMtx.Unlock() | ||
|
||
entry.mu.Lock() | ||
defer entry.mu.Unlock() | ||
if entry.ts.IsZero() || c.clock.Now().Sub(entry.ts) >= cacheExpiry { | ||
bundle, err := c.ds.FetchBundle(ctx, td.IDString()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if bundle == nil { | ||
c.deleteEntry(td) | ||
return nil, nil | ||
} | ||
|
||
entry.ts = c.clock.Now() | ||
if proto.Equal(entry.bundle, bundle) { | ||
return entry.x509Bundle, nil | ||
} | ||
x509Bundle, err := parseBundle(td, bundle) | ||
if err != nil { | ||
return nil, err | ||
} | ||
entry.x509Bundle = x509Bundle | ||
entry.bundle = bundle | ||
} | ||
return entry.x509Bundle, nil | ||
} | ||
|
||
func (c *Cache) deleteEntry(td spiffeid.TrustDomain) { | ||
c.bundlesMtx.Lock() | ||
delete(c.bundles, td) | ||
c.bundlesMtx.Unlock() | ||
} | ||
|
||
// parseBundle parses a *x509bundle.Bundle from a *common.bundle. | ||
func parseBundle(td spiffeid.TrustDomain, commonBundle *common.Bundle) (*x509bundle.Bundle, error) { | ||
var caCerts []*x509.Certificate | ||
for _, rootCA := range commonBundle.RootCas { | ||
rootCACerts, err := x509.ParseCertificates(rootCA.DerBytes) | ||
if err != nil { | ||
return nil, fmt.Errorf("parse bundle: %w", err) | ||
} | ||
caCerts = append(caCerts, rootCACerts...) | ||
} | ||
|
||
return x509bundle.FromX509Authorities(td, caCerts), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package bundle | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/spiffe/spire/test/clock" | ||
|
||
"github.com/spiffe/go-spiffe/v2/bundle/x509bundle" | ||
"github.com/spiffe/go-spiffe/v2/spiffeid" | ||
"github.com/spiffe/spire/proto/spire/common" | ||
"github.com/spiffe/spire/test/fakes/fakedatastore" | ||
"github.com/spiffe/spire/test/testca" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestFetchBundleX509(t *testing.T) { | ||
td := spiffeid.RequireTrustDomainFromString("spiffe://domain.test") | ||
ca := testca.New(t, td) | ||
certs1, _ := ca.CreateX509Certificate() | ||
certs2, _ := ca.CreateX509Certificate() | ||
|
||
bundleX509Response := x509bundle.FromX509Authorities(td, certs1) | ||
updatedBundleX509Response := x509bundle.FromX509Authorities(td, certs2) | ||
bundle1 := &common.Bundle{TrustDomainId: "spiffe://domain.test", RefreshHint: 1, RootCas: []*common.Certificate{{DerBytes: certs1[0].Raw}}} | ||
bundle2 := &common.Bundle{TrustDomainId: "spiffe://domain.test", RefreshHint: 2, RootCas: []*common.Certificate{{DerBytes: certs2[0].Raw}}} | ||
ds := fakedatastore.New(t) | ||
clock := clock.NewMock(t) | ||
cache := NewCache(ds, clock) | ||
ctx := context.Background() | ||
|
||
// Assert bundle is missing | ||
bundleX509, err := cache.FetchBundleX509(ctx, td) | ||
require.NoError(t, err) | ||
require.Nil(t, bundleX509) | ||
|
||
// Add bundle | ||
_, err = ds.SetBundle(ctx, bundle1) | ||
require.NoError(t, err) | ||
|
||
// Assert that we didn't cache the bundle miss and that the newly added | ||
// bundle is there | ||
bundleX509, err = cache.FetchBundleX509(ctx, td) | ||
require.NoError(t, err) | ||
assert.Equal(t, bundleX509Response, bundleX509) | ||
|
||
// Change bundle | ||
_, err = ds.SetBundle(context.Background(), bundle2) | ||
require.NoError(t, err) | ||
|
||
// Assert bundle contents unchanged since cache is still valid | ||
bundleX509, err = cache.FetchBundleX509(ctx, td) | ||
require.NoError(t, err) | ||
assert.Equal(t, bundleX509Response, bundleX509) | ||
|
||
// If caches expires by time, FetchBundleX509 must fetch a fresh bundle | ||
clock.Add(cacheExpiry) | ||
bundleX509, err = cache.FetchBundleX509(ctx, td) | ||
require.NoError(t, err) | ||
assert.Equal(t, updatedBundleX509Response, bundleX509) | ||
|
||
// If caches expires by time, but bundle didn't change, FetchBundleX509 must fetch a fresh bundle | ||
clock.Add(cacheExpiry) | ||
bundleX509, err = cache.FetchBundleX509(ctx, td) | ||
require.NoError(t, err) | ||
assert.Equal(t, updatedBundleX509Response, bundleX509) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters