-
Notifications
You must be signed in to change notification settings - Fork 563
/
cacerts.go
146 lines (126 loc) · 3.53 KB
/
cacerts.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package cacerts
import (
"context"
"errors"
"os"
"github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sync/errgroup"
)
const (
EngineCustomCACertsDir = "/usr/local/share/ca-certificates"
)
// Installer is an implementation of installing+uninstalling custom CA certs for a container,
// usually based on the distro.
type Installer interface {
// Install installs the custom CA certs into the container. In case of an error part way through,
// it should attempt to cleanup after itself and return the error. If cleanup itself errors, it should
// be returned wrapped in a CleanupErr type.
Install(ctx context.Context) error
// Uninstall removes the custom CA certs from the container.
Uninstall(context.Context) error
// detect checks if the container is a match for this installer.
detect() (bool, error)
// initialize sets the installer's initial internal state
initialize(*containerFS) error
}
type executeContainerFunc func(ctx context.Context, args ...string) error
func NewInstaller(
ctx context.Context,
spec *specs.Spec,
executeContainer executeContainerFunc,
) (Installer, error) {
dirEnts, err := os.ReadDir(EngineCustomCACertsDir)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
}
if len(dirEnts) == 0 {
return noopInstaller{}, nil
}
ctrFS, err := newContainerFS(spec, executeContainer)
if err != nil {
return nil, err
}
// Run detection in parallel but unblock as soon as one match is found
// in which case it will be used. This way, we only block on every detect
// finishing in the case where no match is found or any error happens.
var eg errgroup.Group
matchChan := make(chan Installer, 1)
for _, installer := range []Installer{
&debianLike{},
&rhelLike{},
} {
installer := installer
eg.Go(func() error {
if err := installer.initialize(ctrFS); err != nil {
return err
}
match, err := installer.detect()
if err != nil {
return err
}
if match {
select {
case matchChan <- installer:
default:
}
}
return nil
})
}
errChan := make(chan error, 1)
go func() {
errChan <- eg.Wait()
}()
select {
case match := <-matchChan:
return match, nil
case err := <-errChan:
// double check there wasn't an obscure race condition where a match
// was found but we weren't signaled until after the errgroup finished
select {
case match := <-matchChan:
return match, nil
default:
}
if err != nil {
return nil, err
}
}
// no match found
return noopInstaller{}, nil
}
type noopInstaller struct{}
func (noopInstaller) Install(context.Context) error { return nil }
func (noopInstaller) Uninstall(context.Context) error { return nil }
func (noopInstaller) detect() (bool, error) { return false, nil }
func (noopInstaller) initialize(*containerFS) error { return nil }
// Want identifiable separate type for cleanup errors since if those are
// hit specifically we need to fail to the whole exec (whereas other errors
// but successful cleanup can be non-fatal)
type CleanupErr struct {
err error
}
func (c CleanupErr) Error() string {
return c.err.Error()
}
func (c CleanupErr) Unwrap() error {
return c.err
}
type cleanups struct {
funcs []func() error
}
func (c *cleanups) append(f func() error) {
c.funcs = append(c.funcs, f)
}
func (c *cleanups) prepend(f func() error) {
c.funcs = append([]func() error{f}, c.funcs...)
}
func (c *cleanups) run() error {
var rerr error
for i := len(c.funcs) - 1; i >= 0; i-- {
if err := c.funcs[i](); err != nil {
rerr = errors.Join(rerr, CleanupErr{err})
}
}
return rerr
}