blob: 227dd6ff576789cb449f3586c47e7bc0a81daefe [file] [log] [blame]
Mohammed Naser168acc32024-01-09 17:15:26 -05001package tls
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "os"
9 "time"
10
11 cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
12 cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
13 cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1"
14 log "github.com/sirupsen/logrus"
15 "github.com/vexxhost/atmosphere/internal/net"
16 v1 "k8s.io/api/core/v1"
17 "k8s.io/apimachinery/pkg/api/errors"
18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19 "k8s.io/apimachinery/pkg/fields"
20 "k8s.io/apimachinery/pkg/runtime"
21 "k8s.io/apimachinery/pkg/types"
22 "k8s.io/apimachinery/pkg/util/wait"
23 "k8s.io/apimachinery/pkg/watch"
24 kubernetes "k8s.io/client-go/kubernetes/typed/core/v1"
25 "k8s.io/client-go/rest"
26 "k8s.io/client-go/tools/cache"
27)
28
29type LibvirtCertificateType string
30
31const (
32 LibvirtCertificateTypeAPI LibvirtCertificateType = "api"
33 LibvirtCertificateTypeVNC LibvirtCertificateType = "vnc"
34)
35
36const (
37 EnvVarPodUID = "POD_UID"
38 EnvVarPodName = "POD_NAME"
39 EnvVarPodNamespace = "POD_NAMESPACE"
40 EnvVarPodIP = "POD_IP"
41)
42
43type LibvirtCertificateSpec struct {
44 Type LibvirtCertificateType
45 IssuerRef cmmeta.ObjectReference
46}
47
48type LibvirtManager struct {
49 logger *log.Entry
50 spec *LibvirtCertificateSpec
51 certificate *cmv1.Certificate
52 certificateName string
53 certificateClient cmclient.CertificateInterface
54 secretClient kubernetes.SecretInterface
55}
56
57func NewLibvirtManager(config *rest.Config, spec *LibvirtCertificateSpec) (*LibvirtManager, error) {
58 required := []string{
59 EnvVarPodName,
60 EnvVarPodNamespace,
61 EnvVarPodUID,
62 EnvVarPodIP,
63 }
64
65 for _, env := range required {
66 if os.Getenv(env) == "" {
67 return nil, fmt.Errorf("missing required environment variable: %s", env)
68 }
69 }
70
71 mgr := &LibvirtManager{}
72
73 hostname, err := net.Hostname()
74 if err != nil {
75 return nil, err
76 }
77
78 fqdn, err := net.FQDN()
79 if err != nil {
80 return nil, err
81 }
82
83 clientset, err := kubernetes.NewForConfig(config)
84 if err != nil {
85 return nil, err
86 }
87
88 cmClient, err := cmclient.NewForConfig(config)
89 if err != nil {
90 return nil, err
91 }
92
93 podUID := types.UID(os.Getenv(EnvVarPodUID))
94 podNamespace := os.Getenv(EnvVarPodNamespace)
95 podName := os.Getenv(EnvVarPodName)
96 podIP := os.Getenv(EnvVarPodIP)
97
98 mgr.spec = spec
99 mgr.secretClient = clientset.Secrets(podNamespace)
100 mgr.certificateClient = cmClient.Certificates(podNamespace)
101 mgr.certificateName = fmt.Sprintf("%s-%s", podName, spec.Type)
102
103 mgr.logger = log.WithFields(log.Fields{
104 "certificateName": mgr.certificateName,
105 "podName": podName,
106 "podNamespace": podNamespace,
107 "podUID": podUID,
108 "podIP": podIP,
109 "hostname": hostname,
110 "fqdn": fqdn,
111 "issuerKind": spec.IssuerRef.Kind,
112 "issuerName": spec.IssuerRef.Name,
113 })
114
115 mgr.certificate = &cmv1.Certificate{
116 ObjectMeta: metav1.ObjectMeta{
117 Name: mgr.certificateName,
118 Namespace: podNamespace,
119 OwnerReferences: []metav1.OwnerReference{
120 {
121 APIVersion: "v1",
122 Kind: "Pod",
123 Name: podName,
124 UID: podUID,
125 },
126 },
127 },
128 Spec: cmv1.CertificateSpec{
129 SecretName: mgr.certificateName,
130 CommonName: podIP,
131 Usages: []cmv1.KeyUsage{
132 cmv1.UsageClientAuth,
133 cmv1.UsageServerAuth,
134 },
135 DNSNames: []string{hostname, fqdn},
136 IPAddresses: []string{podIP},
137 IssuerRef: spec.IssuerRef,
138 },
139 }
140
141 return mgr, nil
142}
143
144func (m *LibvirtManager) Create(ctx context.Context) error {
145 // Create certificate
146 _, err := m.certificateClient.Create(ctx, m.certificate, metav1.CreateOptions{})
147 if err != nil && !errors.IsAlreadyExists(err) {
148 return err
149 }
150
151 m.logger.Info("certificate created")
152
153 // Wait for certificate to become ready
154 err = wait.PollUntilContextTimeout(ctx, 5*time.Second, 300*time.Second, true, func(ctx context.Context) (bool, error) {
155 certificate, err := m.certificateClient.Get(ctx, m.certificateName, metav1.GetOptions{})
156 if err != nil {
157 return false, err
158 }
159
160 for _, condition := range certificate.Status.Conditions {
161 if condition.Type == cmv1.CertificateConditionReady {
162 if condition.Status == cmmeta.ConditionTrue {
163 return true, nil
164 }
165
166 m.logger.WithFields(log.Fields{
167 "reason": condition.Reason,
168 "message": condition.Message,
169 }).Info("certificate not ready")
170 }
171 }
172
173 return false, nil
174 })
175 if err != nil {
176 return err
177 }
178
179 m.logger.Info("certificate ready")
180
181 // Create patch with ownerReference so the secret is garbage collected
182 patch := []map[string]interface{}{
183 {
184 "op": "add",
185 "path": "/metadata/ownerReferences",
186 "value": m.certificate.OwnerReferences,
187 },
188 }
189 patchBytes, err := json.Marshal(patch)
190 if err != nil {
191 return err
192 }
193
194 m.logger.Info("patching secret")
195
196 // Patch secret with ownerReference
197 _, err = m.secretClient.Patch(ctx, m.certificateName, types.JSONPatchType, patchBytes, metav1.PatchOptions{})
198 return err
199}
200
201func (m *LibvirtManager) Watch(ctx context.Context) {
202 for {
203 m.watch(ctx)
204 m.logger.Info("watch closed or disconnected, retrying in 5 seconds")
205
206 time.Sleep(5 * time.Second)
207 }
208}
209
210func (m *LibvirtManager) watch(ctx context.Context) {
211 fieldSelector := fields.OneTermEqualSelector("metadata.name", m.certificateName).String()
212
213 listWatcher := &cache.ListWatch{
214 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
215 options.FieldSelector = fieldSelector
216 return m.secretClient.List(ctx, options)
217 },
218 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
219 options.FieldSelector = fieldSelector
220 return m.secretClient.Watch(ctx, options)
221 },
222 }
223
224 _, controller := cache.NewInformer(
225 listWatcher,
226 &v1.Secret{},
227 time.Minute,
228 cache.ResourceEventHandlerFuncs{
229 AddFunc: func(obj interface{}) {
230 secret := obj.(*v1.Secret)
231 m.write(secret)
232 },
233 UpdateFunc: func(oldObj, newObj interface{}) {
234 secret := newObj.(*v1.Secret)
235 m.write(secret)
236 },
237 DeleteFunc: func(obj interface{}) {
238 m.logger.Fatal("secret deleted")
239 },
240 },
241 )
242
243 stop := make(chan struct{})
244 defer close(stop)
245 controller.Run(stop)
246}
247
248func (m *LibvirtManager) write(secret *v1.Secret) {
249 switch m.spec.Type {
250 case LibvirtCertificateTypeAPI:
251 m.createDirectory("/etc/pki/libvirt/private")
252 m.writeFile("/etc/pki/CA/cacert.pem", secret.Data["ca.crt"])
253 m.writeFile("/etc/pki/libvirt/servercert.pem", secret.Data["tls.crt"])
254 m.writeFile("/etc/pki/libvirt/private/serverkey.pem", secret.Data["tls.key"])
255 m.writeFile("/etc/pki/libvirt/clientcert.pem", secret.Data["tls.crt"])
Mohammed Naser36688522024-01-22 13:19:19 -0500256 m.writeFile("/etc/pki/libvirt/private/clientkey.pem", secret.Data["tls.key"])
Mohammed Naser168acc32024-01-09 17:15:26 -0500257 m.createDirectory("/etc/pki/qemu")
258 m.writeFile("/etc/pki/qemu/ca-cert.pem", secret.Data["ca.crt"])
259 m.writeFile("/etc/pki/qemu/server-cert.pem", secret.Data["tls.crt"])
260 m.writeFile("/etc/pki/qemu/server-key.pem", secret.Data["tls.key"])
261 m.writeFile("/etc/pki/qemu/client-cert.pem", secret.Data["tls.crt"])
262 m.writeFile("/etc/pki/qemu/client-key.pem", secret.Data["tls.key"])
263 case LibvirtCertificateTypeVNC:
264 m.createDirectory("/etc/pki/libvirt-vnc")
265 m.writeFile("/etc/pki/libvirt-vnc/ca-cert.pem", secret.Data["ca.crt"])
266 m.writeFile("/etc/pki/libvirt-vnc/server-cert.pem", secret.Data["tls.crt"])
267 m.writeFile("/etc/pki/libvirt-vnc/server-key.pem", secret.Data["tls.key"])
268 }
269}
270
271func (m *LibvirtManager) createDirectory(path string) {
272 if _, err := os.Stat(path); !os.IsNotExist(err) {
273 return
274 }
275
276 m.logger.WithFields(log.Fields{
277 "path": path,
278 }).Info("creating directory")
279
280 err := os.MkdirAll(path, 0755)
281 if err != nil {
282 m.logger.Fatal(err)
283 }
284}
285
286func (m *LibvirtManager) writeFile(path string, data []byte) {
287 log := m.logger.WithFields(log.Fields{
288 "path": path,
289 })
290
291 existingData, err := os.ReadFile(path)
292 if err != nil {
293 if os.IsNotExist(err) {
294 log.Info("file does not exist, creating file")
295
296 err = os.WriteFile(path, data, 0644)
297 if err != nil {
298 log.Fatal(err)
299 }
300
301 return
302 }
303
304 m.logger.Fatal(err)
305 }
306
307 if bytes.Equal(existingData, data) {
308 return
309 }
310
311 log.Info("file contents changed, updating file")
312
313 err = os.WriteFile(path, data, 0644)
314 if err != nil {
315 log.Fatal(err)
316 }
317}