Skip to content

Commit b46a62d

Browse files
committed
concurrency/dir & WriteIdentityToFile
Adds concurrency/dir package to handle atomically writing files to a directory. Adds support for spiffe to optionally write the identity certificate, private key and trust bundle to a given directory. Signed-off-by: joshvanl <[email protected]>
1 parent fb19570 commit b46a62d

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

concurrency/dir/dir.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
Copyright 2025 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package dir
15+
16+
import (
17+
"fmt"
18+
"os"
19+
"path/filepath"
20+
"time"
21+
22+
"github.com/dapr/kit/logger"
23+
)
24+
25+
type Options struct {
26+
Log logger.Logger
27+
Target string
28+
}
29+
30+
type Dir struct {
31+
log logger.Logger
32+
33+
base string
34+
target string
35+
targetDir string
36+
37+
prev *string
38+
}
39+
40+
func New(opts Options) *Dir {
41+
return &Dir{
42+
log: opts.Log,
43+
base: filepath.Dir(opts.Target),
44+
target: opts.Target,
45+
targetDir: filepath.Base(opts.Target),
46+
}
47+
}
48+
49+
func (d *Dir) Write(files map[string][]byte) error {
50+
newDir := filepath.Join(d.base, fmt.Sprintf("%d-%s", time.Now().UTC().UnixNano(), d.targetDir))
51+
52+
if err := os.MkdirAll(d.base, os.ModePerm); err != nil {
53+
return err
54+
}
55+
56+
if err := os.MkdirAll(newDir, os.ModePerm); err != nil {
57+
return err
58+
}
59+
60+
for file, b := range files {
61+
path := filepath.Join(newDir, file)
62+
if err := os.WriteFile(path, b, os.ModePerm); err != nil {
63+
return err
64+
}
65+
d.log.Infof("Written file %s", file)
66+
}
67+
68+
if err := os.Symlink(newDir, d.target+".new"); err != nil {
69+
return err
70+
}
71+
72+
d.log.Infof("Syslink %s to %s.new", newDir, d.target)
73+
74+
if err := os.Rename(d.target+".new", d.target); err != nil {
75+
return err
76+
}
77+
78+
d.log.Infof("Atomic write to %s", d.target)
79+
80+
if d.prev != nil {
81+
if err := os.RemoveAll(*d.prev); err != nil {
82+
return err
83+
}
84+
}
85+
86+
d.prev = &newDir
87+
88+
return nil
89+
}

crypto/spiffe/spiffe.go

+49
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import (
2828
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
2929
"k8s.io/utils/clock"
3030

31+
"github.com/dapr/kit/concurrency/dir"
32+
"github.com/dapr/kit/crypto/pem"
33+
"github.com/dapr/kit/crypto/spiffe/trustanchors"
3134
"github.com/dapr/kit/logger"
3235
)
3336

@@ -38,6 +41,14 @@ type (
3841
type Options struct {
3942
Log logger.Logger
4043
RequestSVIDFn RequestSVIDFn
44+
45+
// WriteIdentityToFile is used to write the identity private key and
46+
// certificate chain to file. The certificate chain and private key will be
47+
// written to the `tls.cert` and `tls.key` files respectively in the given
48+
// directory.
49+
WriteIdentityToFile *string
50+
51+
TrustAnchors trustanchors.Interface
4152
}
4253

4354
// SPIFFE is a readable/writeable store of a SPIFFE X.509 SVID.
@@ -46,6 +57,9 @@ type SPIFFE struct {
4657
currentSVID *x509svid.SVID
4758
requestSVIDFn RequestSVIDFn
4859

60+
dir *dir.Dir
61+
trustAnchors trustanchors.Interface
62+
4963
log logger.Logger
5064
lock sync.RWMutex
5165
clock clock.Clock
@@ -54,8 +68,18 @@ type SPIFFE struct {
5468
}
5569

5670
func New(opts Options) *SPIFFE {
71+
var sdir *dir.Dir
72+
if opts.WriteIdentityToFile != nil {
73+
sdir = dir.New(dir.Options{
74+
Log: opts.Log,
75+
Target: *opts.WriteIdentityToFile,
76+
})
77+
}
78+
5779
return &SPIFFE{
5880
requestSVIDFn: opts.RequestSVIDFn,
81+
dir: sdir,
82+
trustAnchors: opts.TrustAnchors,
5983
log: opts.Log,
6084
clock: clock.RealClock{},
6185
readyCh: make(chan struct{}),
@@ -165,6 +189,31 @@ func (s *SPIFFE) fetchIdentityCertificate(ctx context.Context) (*x509svid.SVID,
165189
return nil, fmt.Errorf("error parsing spiffe id from newly signed certificate: %w", err)
166190
}
167191

192+
if s.dir != nil {
193+
pkPEM, err := pem.EncodePrivateKey(key)
194+
if err != nil {
195+
return nil, err
196+
}
197+
198+
certPEM, err := pem.EncodeX509Chain(workloadcert)
199+
if err != nil {
200+
return nil, err
201+
}
202+
203+
td, err := s.trustAnchors.CurrentTrustAnchors(ctx)
204+
if err != nil {
205+
return nil, err
206+
}
207+
208+
if err := s.dir.Write(map[string][]byte{
209+
"key.pem": pkPEM,
210+
"cert.pem": certPEM,
211+
"ca.pem": td,
212+
}); err != nil {
213+
return nil, err
214+
}
215+
}
216+
168217
return &x509svid.SVID{
169218
ID: spiffeID,
170219
Certificates: workloadcert,

0 commit comments

Comments
 (0)