-
Notifications
You must be signed in to change notification settings - Fork 444
/
run.go
164 lines (143 loc) · 5.37 KB
/
run.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
package run
import (
"context"
"time"
"github.com/rotisserie/eris"
"github.com/solo-io/gloo/jobs/pkg/certgen"
"github.com/solo-io/gloo/jobs/pkg/kube"
"github.com/solo-io/gloo/projects/gloo/cli/pkg/helpers"
"github.com/solo-io/go-utils/contextutils"
"go.uber.org/zap"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
)
// Options to configure the rotation job
// Certgen job yaml files have opinionated defaults for these options
type Options struct {
SvcName string
SvcNamespace string
SecretName string
SecretNamespace string
ServerCertSecretFileName string
ServerCertAuthorityFileName string
ServerKeySecretFileName string
ValidatingWebhookConfigurationName string
ForceRotation bool
RenewBefore string
// The duration waited after first updating a secret's cabundle before
// updating the actual secrets.
// Lower values make the rotation job run faster
// Higher values make the rotation job more resilient to errors
RotationDuration string
}
func Run(ctx context.Context, opts Options) error {
if opts.SvcNamespace == "" {
return eris.Errorf("must provide svc-namespace")
}
if opts.SvcName == "" {
return eris.Errorf("must provide svc-name")
}
if opts.SecretNamespace == "" {
return eris.Errorf("must provide secret-namespace")
}
if opts.SecretName == "" {
return eris.Errorf("must provide secret-name")
}
if opts.ServerCertSecretFileName == "" {
return eris.Errorf("must provide name for the server cert entry in the secret data")
}
if opts.ServerCertAuthorityFileName == "" {
return eris.Errorf("must provide name for the cert authority entry in the secret data")
}
if opts.ServerKeySecretFileName == "" {
return eris.Errorf("must provide name for the server key entry in the secret data")
}
renewBeforeDuration, err := time.ParseDuration(opts.RenewBefore)
if err != nil {
return err
}
rotationDuration, err := time.ParseDuration(opts.RotationDuration)
if err != nil {
return err
}
logger := contextutils.LoggerFrom(ctx)
logger.Info("starting certgen job, validated inputs")
kubeClient := helpers.MustKubeClient()
// check if there is an existing valid TLS secret
secret, expiringSoon, err := kube.GetExistingValidTlsSecret(ctx, kubeClient, opts.SecretName, opts.SecretNamespace,
opts.SvcName, opts.SvcNamespace, renewBeforeDuration)
if err != nil {
return eris.Wrapf(err, "failed validating existing secret")
}
if secret == nil {
logger.Info("no existing valid secret, generating a new one...")
// generate a new one
certs, err := certgen.GenCerts(opts.SvcName, opts.SvcNamespace)
if err != nil {
return eris.Wrapf(err, "generating self-signed certs and key")
}
secretConfig := kube.TlsSecret{
SecretName: opts.SecretName,
SecretNamespace: opts.SecretNamespace,
PrivateKeyFileName: opts.ServerKeySecretFileName,
CertFileName: opts.ServerCertSecretFileName,
CaBundleFileName: opts.ServerCertAuthorityFileName,
PrivateKey: certs.ServerCertKey,
Cert: certs.ServerCertificate,
CaBundle: certs.CaCertificate,
}
secret, err = kube.CreateTlsSecret(ctx, kubeClient, secretConfig)
if err != nil {
return eris.Wrapf(err, "failed creating secret")
}
} else if expiringSoon || opts.ForceRotation {
logger.Info("secret exists but need to rotate...")
// current secret
tlsSecret := parseTlsSecret(secret, opts)
// newly generated certs to rotate in
nextCerts, err := certgen.GenCerts(opts.SvcName, opts.SvcNamespace)
if err != nil {
return eris.Wrapf(err, "generating self-signed certs and key")
}
secret, err = kube.RotateCerts(ctx, kubeClient, tlsSecret, nextCerts, rotationDuration)
if err != nil {
return eris.Wrapf(err, "failed to rotate certs")
}
} else {
// cert is still good
contextutils.LoggerFrom(ctx).Infow("existing TLS secret found, skipping update to TLS secret since the old TLS secret is still valid",
zap.String("secretName", opts.SecretName),
zap.String("secretNamespace", opts.SecretNamespace))
}
return persistWebhook(ctx, opts, kubeClient, secret)
}
func persistWebhook(ctx context.Context, opts Options, kubeClient kubernetes.Interface, secret *v1.Secret) error {
vwcName := opts.ValidatingWebhookConfigurationName
if vwcName == "" {
contextutils.LoggerFrom(ctx).Infof("no ValidatingWebhookConfiguration provided. finished successfully.")
return nil
}
vwcConfig := kube.WebhookTlsConfig{
ServiceName: opts.SvcName,
ServiceNamespace: opts.SvcNamespace,
CaBundle: secret.Data[opts.ServerCertAuthorityFileName],
}
if err := kube.UpdateValidatingWebhookConfigurationCaBundle(ctx, kubeClient, vwcName, vwcConfig); err != nil {
return eris.Wrapf(err, "failed patching validating webhook config")
}
contextutils.LoggerFrom(ctx).Infof("finished successfully.")
return nil
}
// construct a TlsSecret from an existing Secret
func parseTlsSecret(secret *v1.Secret, opts Options) kube.TlsSecret {
return kube.TlsSecret{
SecretName: secret.GetObjectMeta().GetName(),
SecretNamespace: secret.GetObjectMeta().GetNamespace(),
PrivateKeyFileName: opts.ServerKeySecretFileName,
CertFileName: opts.ServerCertSecretFileName,
CaBundleFileName: opts.ServerCertAuthorityFileName,
PrivateKey: secret.Data[opts.ServerKeySecretFileName],
Cert: secret.Data[opts.ServerCertSecretFileName],
CaBundle: secret.Data[opts.ServerCertAuthorityFileName],
}
}