Skip to content

Commit 1f6b0cf

Browse files
authored
[Security] Add support for SPIFFE Bundle Maps in certificate providers (#8167)
1 parent 775150f commit 1f6b0cf

File tree

16 files changed

+470
-152
lines changed

16 files changed

+470
-152
lines changed

credentials/tls/certprovider/pemfile/builder_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func TestParseConfig(t *testing.T) {
9494
"private_key_file": "/a/b/key.pem",
9595
"ca_certificate_file": "/a/b/ca.pem"
9696
}`),
97-
wantOutput: "file_watcher:/a/b/cert.pem:/a/b/key.pem:/a/b/ca.pem:10m0s",
97+
wantOutput: "file_watcher:/a/b/cert.pem:/a/b/key.pem:/a/b/ca.pem::10m0s",
9898
},
9999
{
100100
desc: "good config",
@@ -105,7 +105,7 @@ func TestParseConfig(t *testing.T) {
105105
"ca_certificate_file": "/a/b/ca.pem",
106106
"refresh_interval": "200s"
107107
}`),
108-
wantOutput: "file_watcher:/a/b/cert.pem:/a/b/key.pem:/a/b/ca.pem:3m20s",
108+
wantOutput: "file_watcher:/a/b/cert.pem:/a/b/key.pem:/a/b/ca.pem::3m20s",
109109
},
110110
}
111111

credentials/tls/certprovider/pemfile/watcher.go

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838

3939
"google.golang.org/grpc/credentials/tls/certprovider"
4040
"google.golang.org/grpc/grpclog"
41+
"google.golang.org/grpc/internal/credentials/spiffe"
4142
)
4243

4344
const defaultCertRefreshDuration = 1 * time.Hour
@@ -61,18 +62,23 @@ type Options struct {
6162
// RootFile is the file that holds trusted root certificate(s).
6263
// Optional.
6364
RootFile string
65+
// SPIFFEBundleMapFile is the file that holds the spiffe bundle map.
66+
// If a given provider configures both the RootFile and the
67+
// SPIFFEBundleMapFile, the SPIFFEBundleMapFile will be preferred.
68+
// Optional.
69+
SPIFFEBundleMapFile string
6470
// RefreshDuration is the amount of time the plugin waits before checking
6571
// for updates in the specified files.
6672
// Optional. If not set, a default value (1 hour) will be used.
6773
RefreshDuration time.Duration
6874
}
6975

7076
func (o Options) canonical() []byte {
71-
return []byte(fmt.Sprintf("%s:%s:%s:%s", o.CertFile, o.KeyFile, o.RootFile, o.RefreshDuration))
77+
return []byte(fmt.Sprintf("%s:%s:%s:%s:%s", o.CertFile, o.KeyFile, o.RootFile, o.SPIFFEBundleMapFile, o.RefreshDuration))
7278
}
7379

7480
func (o Options) validate() error {
75-
if o.CertFile == "" && o.KeyFile == "" && o.RootFile == "" {
81+
if o.CertFile == "" && o.KeyFile == "" && o.RootFile == "" && o.SPIFFEBundleMapFile == "" {
7682
return fmt.Errorf("pemfile: at least one credential file needs to be specified")
7783
}
7884
if keySpecified, certSpecified := o.KeyFile != "", o.CertFile != ""; keySpecified != certSpecified {
@@ -124,13 +130,14 @@ func newProvider(o Options) certprovider.Provider {
124130
// files and provides the most up-to-date key material for consumption by
125131
// credentials implementation.
126132
type watcher struct {
127-
identityDistributor distributor
128-
rootDistributor distributor
129-
opts Options
130-
certFileContents []byte
131-
keyFileContents []byte
132-
rootFileContents []byte
133-
cancel context.CancelFunc
133+
identityDistributor distributor
134+
rootDistributor distributor
135+
opts Options
136+
certFileContents []byte
137+
keyFileContents []byte
138+
rootFileContents []byte
139+
spiffeBundleMapFileContents []byte
140+
cancel context.CancelFunc
134141
}
135142

136143
// distributor wraps the methods on certprovider.Distributor which are used by
@@ -191,14 +198,43 @@ func (w *watcher) updateRootDistributor() {
191198
return
192199
}
193200

201+
// If SPIFFEBundleMap is set, use it and DON'T use the RootFile, even if it
202+
// fails
203+
if w.opts.SPIFFEBundleMapFile != "" {
204+
w.maybeUpdateSPIFFEBundleMap()
205+
} else {
206+
w.maybeUpdateRootFile()
207+
}
208+
}
209+
210+
func (w *watcher) maybeUpdateSPIFFEBundleMap() {
211+
spiffeBundleMapContents, err := os.ReadFile(w.opts.SPIFFEBundleMapFile)
212+
if err != nil {
213+
logger.Warningf("spiffeBundleMapFile (%s) read failed: %v", w.opts.SPIFFEBundleMapFile, err)
214+
return
215+
}
216+
// If the file contents have not changed, skip updating the distributor.
217+
if bytes.Equal(w.spiffeBundleMapFileContents, spiffeBundleMapContents) {
218+
return
219+
}
220+
bundleMap, err := spiffe.BundleMapFromBytes(spiffeBundleMapContents)
221+
if err != nil {
222+
logger.Warning("Failed to parse spiffe bundle map")
223+
return
224+
}
225+
w.spiffeBundleMapFileContents = spiffeBundleMapContents
226+
w.rootDistributor.Set(&certprovider.KeyMaterial{SPIFFEBundleMap: bundleMap}, nil)
227+
}
228+
229+
func (w *watcher) maybeUpdateRootFile() {
194230
rootFileContents, err := os.ReadFile(w.opts.RootFile)
195231
if err != nil {
196232
logger.Warningf("rootFile (%s) read failed: %v", w.opts.RootFile, err)
197233
return
198234
}
199235
trustPool := x509.NewCertPool()
200236
if !trustPool.AppendCertsFromPEM(rootFileContents) {
201-
logger.Warning("failed to parse root certificate")
237+
logger.Warning("Failed to parse root certificate")
202238
return
203239
}
204240
// If the file contents have not changed, skip updating the distributor.
@@ -249,6 +285,7 @@ func (w *watcher) KeyMaterial(ctx context.Context) (*certprovider.KeyMaterial, e
249285
if err != nil {
250286
return nil, err
251287
}
288+
km.SPIFFEBundleMap = rootKM.SPIFFEBundleMap
252289
km.Roots = rootKM.Roots
253290
}
254291
return km, nil

0 commit comments

Comments
 (0)