This repository has been archived by the owner on Mar 24, 2022. It is now read-only.
forked from cloudfoundry/bosh-agent
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cert_manager.go
185 lines (161 loc) · 5.99 KB
/
cert_manager.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package cert
import (
"fmt"
"strings"
"time"
bosherr "github.com/cloudfoundry/bosh-utils/errors"
"github.com/cloudfoundry/bosh-utils/logger"
boshsys "github.com/cloudfoundry/bosh-utils/system"
)
//go:generate counterfeiter . Manager
// Manager is a set of operations for manipulating the set of trusted CA certificates
// on any OS platform.
type Manager interface {
// UpdateCertificates manages the set of CA certificates that are trusted in
// addition to certificates that were pre-installed on the operating system.
//
// Each call alters the set of X.509 certificates that are trusted as
// root certificates on this machine to match the set of certificates given.
//
// Calling this method again later with a different set of certificates will
// replace the previously trusted certificates with the new set; hence, calling
// this method with an empty set of certificates will bring this machine back to
// the initial state, where it only trusts the CA certificates that came with the OS.
//
// The certs argument should contain zero or more X.509 certificates in PEM format
// concatenated together. Any text that is not between `-----BEGIN CERTIFICATE-----`
// and `-----END CERTIFICATE-----` lines is ignored.
UpdateCertificates(certs string) error
}
type certManager struct {
fs boshsys.FileSystem
runner boshsys.CmdRunner
path string
updateCmdPath string
updateCmdArgs []string
logger logger.Logger
logTag string
// Update execution time limit in seconds
// No retry if 0
updateTimeout time.Duration
}
func NewUbuntuCertManager(fs boshsys.FileSystem, runner boshsys.CmdRunner, timeout time.Duration, logger logger.Logger) Manager {
return &certManager{
fs: fs,
runner: runner,
path: "/usr/local/share/ca-certificates/",
updateCmdPath: "/usr/sbin/update-ca-certificates",
updateCmdArgs: []string{"-f"},
logger: logger,
logTag: "UbuntuCertManager",
updateTimeout: timeout,
}
}
func NewCentOSCertManager(fs boshsys.FileSystem, runner boshsys.CmdRunner, timeout time.Duration, logger logger.Logger) Manager {
return &certManager{
fs: fs,
runner: runner,
path: "/etc/pki/ca-trust/source/anchors/",
updateCmdPath: "/usr/bin/update-ca-trust",
logger: logger,
logTag: "CentOSCertManager",
updateTimeout: timeout,
}
}
func NewDummyCertManager(fs boshsys.FileSystem, runner boshsys.CmdRunner, timeout time.Duration, logger logger.Logger) Manager {
return &certManager{
fs: fs,
runner: runner,
path: "dummy",
updateCmdPath: "dummy",
logger: logger,
logTag: "DummyCertManager",
updateTimeout: timeout,
}
}
func (c *certManager) UpdateCertificates(certs string) error {
c.logger.Info(c.logTag, "Running Update Certificate command")
if c.updateCmdPath == "dummy" {
return nil
}
deletedFilesCount, err := deleteFiles(c.fs, c.path, "bosh-trusted-cert-")
c.logger.Debug(c.logTag, "Deleted %d existing certificate files", deletedFilesCount)
if err != nil {
return err
}
slicedCerts := splitCerts(certs)
for i, cert := range slicedCerts {
err := c.fs.WriteFileString(fmt.Sprintf("%sbosh-trusted-cert-%d.crt", c.path, i+1), cert)
if err != nil {
return err
}
}
c.logger.Debug(c.logTag, "Wrote %d new certificate files", len(slicedCerts))
// For Ubuntu OS, update-ca-certificates occasionally hangs, which results
// in bosh-agent failure. A retry normally solves this issue. We kill the process
// if it runs over given time limit and retry for 3 times until we throw error.
if c.updateTimeout > 0 {
command := boshsys.Command{
Name: c.updateCmdPath,
Args: c.updateCmdArgs,
}
for i := 1; i < 4; i++ {
c.logger.Debug(c.logTag, "Try to update new certificate files with retry, take %d of 3", i)
process, err := c.runner.RunComplexCommandAsync(command)
if err != nil {
return bosherr.WrapError(err, "Running command to update certificates with retries")
}
resultChannel := process.Wait()
select {
case <-time.After(c.updateTimeout * time.Second):
err = process.TerminateNicely(5 * time.Second)
if err != nil {
c.logger.Debug(c.logTag, "Failed to terminate update certificates cmd '%s' after %d seconds", c.updateCmdPath, c.updateTimeout)
}
case result := <-resultChannel:
if result.Error == nil {
c.logger.Debug(c.logTag, "Successfully updated new certificate files")
return nil
}
}
}
return bosherr.Error("Updating certificates with retries")
}
c.logger.Debug(c.logTag, "Try to update new certificate files without retry")
_, _, _, err = c.runner.RunCommand(c.updateCmdPath, c.updateCmdArgs...)
if err != nil {
return bosherr.WrapError(err, "Running command to update certificates without retries")
}
c.logger.Debug(c.logTag, "Successfully updated new certificate files.")
return nil
}
// SplitCerts returns a slice containing each PEM certificate in the given string.
// extra data before the first cert, between each cert, and after the last cert
// is all discarded. Each string in the returned slice will begin with
// `-----BEGIN CERTIFICATE-----` and end with `-----END CERTIFICATE-----`
// and have no leading or trailing whitespace.
func splitCerts(certs string) []string {
result := strings.SplitAfter(fmt.Sprintln(certs), "-----END CERTIFICATE-----")
for i := range result {
start := strings.Index(result[i], "-----BEGIN CERTIFICATE-----")
if start > 0 {
result[i] = result[i][start:len(result[i])]
}
}
return result[0 : len(result)-1]
}
func deleteFiles(fs boshsys.FileSystem, path string, filenamePrefix string) (int, error) {
var deletedFilesCount int
files, err := fs.Glob(fmt.Sprintf("%s%s*", path, filenamePrefix))
if err != nil {
return deletedFilesCount, bosherr.WrapError(err, "Glob command failed")
}
for _, file := range files {
err = fs.RemoveAll(file)
if err != nil {
return deletedFilesCount, bosherr.WrapErrorf(err, "deleting %s failed", file)
}
deletedFilesCount++
}
return deletedFilesCount, err
}