blob: c7e6f9e27bf59916d9e3e01d618ad1d27747e93d [file] [log] [blame]
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +04001package tasks
2
3import (
4 "fmt"
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +04005 "net"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +04006 "net/netip"
7
8 "github.com/miekg/dns"
9
10 "github.com/giolekva/pcloud/core/installer"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040011)
12
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040013func SetupInfra(env Env, startIP net.IP, st *state) []Task {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040014 t := newLeafTask("Create client", func() error {
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040015 repo, err := st.ssClient.GetRepo("config")
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040016 if err != nil {
17 return err
18 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040019 r := installer.NewRepoIO(repo, st.ssClient.Signer)
20 appManager, err := installer.NewAppManager(r, st.nsCreator)
21 if err != nil {
22 return err
23 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040024 st.appManager = appManager
25 st.appsRepo = installer.NewInMemoryAppRepository(installer.CreateAllApps())
26 st.nsGen = installer.NewPrefixGenerator(env.Name + "-")
27 st.emptySuffixGen = installer.NewEmptySuffixGenerator()
28 return nil
29 })
30 return []Task{
31 CommitEnvironmentConfiguration(env, st),
32 &t,
33 newConcurrentParentTask(
34 "Core services",
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040035 SetupNetwork(env, startIP, st),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040036 SetupCertificateIssuers(env, st),
37 SetupAuth(env, st),
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040038 SetupGroupMemberships(env, st),
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040039 SetupHeadscale(env, startIP, st),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040040 SetupWelcome(env, st),
41 SetupAppStore(env, st),
42 ),
43 }
44}
45
46func CommitEnvironmentConfiguration(env Env, st *state) Task {
47 t := newLeafTask("Configure environment infrastructure", func() error {
48 repo, err := st.ssClient.GetRepo("config")
49 if err != nil {
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040050 return err
51 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040052 r := installer.NewRepoIO(repo, st.ssClient.Signer)
53 {
54 // TODO(giolekva): private domain can be configurable as well
55 config := installer.Config{
56 Values: installer.Values{
57 PCloudEnvName: env.PCloudEnvName,
58 Id: env.Name,
59 ContactEmail: env.ContactEmail,
60 Domain: env.Domain,
61 PrivateDomain: fmt.Sprintf("p.%s", env.Domain),
62 PublicIP: st.publicIPs[0].String(),
63 NamespacePrefix: fmt.Sprintf("%s-", env.Name),
64 },
65 }
66 if err := r.WriteYaml("config.yaml", config); err != nil {
67 return err
68 }
69 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040070 {
71 out, err := r.Writer("pcloud-charts.yaml")
72 if err != nil {
73 return err
74 }
75 defer out.Close()
76 _, err = fmt.Fprintf(out, `
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040077apiVersion: source.toolkit.fluxcd.io/v1
78kind: GitRepository
79metadata:
80 name: pcloud
81 namespace: %s
82spec:
83 interval: 1m0s
84 url: https://github.com/giolekva/pcloud
85 ref:
Giorgi Lekveishvili024757c2024-03-14 13:27:29 +040086 branch: main
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040087`, env.Name)
88 if err != nil {
89 return err
90 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040091 rootKust, err := r.ReadKustomization("kustomization.yaml")
92 if err != nil {
93 return err
94 }
95 rootKust.AddResources("pcloud-charts.yaml")
96 if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
97 return err
98 }
99 r.CommitAndPush("configure charts repo")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400100 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400101 return nil
102 })
103 return &t
104}
105
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400106func SetupNetwork(env Env, startIP net.IP, st *state) Task {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400107 t := newLeafTask("Setup network", func() error {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400108 startAddr, err := netip.ParseAddr(startIP.String())
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400109 if err != nil {
110 return err
111 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400112 if !startAddr.Is4() {
113 return fmt.Errorf("Expected IPv4, got %s instead", startAddr)
114 }
115 addr := startAddr.AsSlice()
116 if addr[3] != 0 {
117 return fmt.Errorf("Expected last byte to be zero, got %d instead", addr[3])
118 }
119 addr[3] = 10
120 fromIP, ok := netip.AddrFromSlice(addr)
121 if !ok {
122 return fmt.Errorf("Must not reach")
123 }
124 addr[3] = 254
125 toIP, ok := netip.AddrFromSlice(addr)
126 if !ok {
127 return fmt.Errorf("Must not reach")
128 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400129 {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400130 ingressPrivateIP := startAddr
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400131 headscaleIP := ingressPrivateIP.Next()
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400132 app, err := st.appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400133 if err != nil {
134 return err
135 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400136 if err := st.appManager.Install(app, st.nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400137 "name": fmt.Sprintf("%s-ingress-private", env.Name),
138 "from": ingressPrivateIP.String(),
139 "to": ingressPrivateIP.String(),
140 "autoAssign": false,
141 "namespace": "metallb-system",
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400142 }); err != nil {
143 return err
144 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400145 if err := st.appManager.Install(app, st.nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400146 "name": fmt.Sprintf("%s-headscale", env.Name),
147 "from": headscaleIP.String(),
148 "to": headscaleIP.String(),
149 "autoAssign": false,
150 "namespace": "metallb-system",
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400151 }); err != nil {
152 return err
153 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400154 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400155 "name": env.Name,
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400156 "from": fromIP.String(),
157 "to": toIP.String(),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400158 "autoAssign": false,
159 "namespace": "metallb-system",
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400160 }); err != nil {
161 return err
162 }
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400163 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400164 {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400165 app, err := st.appsRepo.Find("private-network")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400166 if err != nil {
167 return err
168 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400169 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400170 "privateNetwork": map[string]any{
171 "hostname": "private-network-proxy",
172 "username": "private-network-proxy",
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400173 "ipSubnet": fmt.Sprintf("%s/24", startIP.String()),
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400174 },
175 }); err != nil {
176 return err
177 }
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400178 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400179 return nil
180 })
181 return &t
182}
183
184func SetupCertificateIssuers(env Env, st *state) Task {
185 pub := newLeafTask(fmt.Sprintf("Public %s", env.Domain), func() error {
186 app, err := st.appsRepo.Find("certificate-issuer-public")
187 if err != nil {
188 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400189 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400190 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400191 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400192 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400193 return nil
194 })
195 priv := newLeafTask(fmt.Sprintf("Private p.%s", env.Domain), func() error {
196 app, err := st.appsRepo.Find("certificate-issuer-private")
197 if err != nil {
198 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400199 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400200 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400201 "apiConfigMap": map[string]any{
202 "name": "api-config", // TODO(gio): take from global pcloud config
203 "namespace": fmt.Sprintf("%s-dns-zone-manager", env.PCloudEnvName),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400204 },
205 }); err != nil {
206 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400207 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400208 return nil
209 })
210 return newSequentialParentTask("Configure TLS certificate issuers", &pub, &priv)
211}
212
213func SetupAuth(env Env, st *state) Task {
214 t := newLeafTask("Setup", func() error {
215 app, err := st.appsRepo.Find("core-auth")
216 if err != nil {
217 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400218 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400219 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400220 "subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400221 }); err != nil {
222 return err
223 }
224 return nil
225 })
226 return newSequentialParentTask(
227 "Authentication services",
228 &t,
229 waitForAddr(fmt.Sprintf("https://accounts-ui.%s", env.Domain)),
230 )
231}
232
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400233func SetupGroupMemberships(env Env, st *state) Task {
234 t := newLeafTask("Setup", func() error {
235 app, err := st.appsRepo.Find("memberships")
236 if err != nil {
237 return err
238 }
239 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{}); err != nil {
240 return err
241 }
242 return nil
243 })
244 return newSequentialParentTask(
245 "Group Membership",
246 &t,
247 waitForAddr(fmt.Sprintf("https://memberships.p.%s", env.Domain)),
248 )
249}
250
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400251func SetupHeadscale(env Env, startIP net.IP, st *state) Task {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400252 t := newLeafTask("Setup", func() error {
253 app, err := st.appsRepo.Find("headscale")
254 if err != nil {
255 return err
256 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400257 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400258 "subdomain": "headscale",
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400259 "ipSubnet": fmt.Sprintf("%s/24", startIP),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400260 }); err != nil {
261 return err
262 }
263 return nil
264 })
265 return newSequentialParentTask(
266 "Headscale service",
267 &t,
268 waitForAddr(fmt.Sprintf("https://headscale.%s/apple", env.Domain)),
269 )
270}
271
272func SetupWelcome(env Env, st *state) Task {
273 t := newLeafTask("Setup", func() error {
274 keys, err := installer.NewSSHKeyPair("welcome")
275 if err != nil {
276 return err
277 }
278 user := fmt.Sprintf("%s-welcome", env.Name)
279 if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
280 return err
281 }
282 if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
283 return err
284 }
285 app, err := st.appsRepo.Find("welcome")
286 if err != nil {
287 return err
288 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400289 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400290 "repoAddr": st.ssClient.GetRepoAddress("config"),
291 "sshPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400292 }); err != nil {
293 return err
294 }
295 return nil
296 })
297 return newSequentialParentTask(
298 "Welcome service",
299 &t,
300 waitForAddr(fmt.Sprintf("https://welcome.%s", env.Domain)),
301 )
302}
303
304func SetupAppStore(env Env, st *state) Task {
305 t := newLeafTask("Application marketplace", func() error {
306 user := fmt.Sprintf("%s-appmanager", env.Name)
307 keys, err := installer.NewSSHKeyPair(user)
308 if err != nil {
309 return err
310 }
311 if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
312 return err
313 }
314 if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
315 return err
316 }
317 app, err := st.appsRepo.Find("app-manager") // TODO(giolekva): configure
318 if err != nil {
319 return err
320 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400321 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400322 "repoAddr": st.ssClient.GetRepoAddress("config"),
323 "sshPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400324 }); err != nil {
325 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400326 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400327 return nil
328 })
329 return &t
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400330}
331
332type DNSSecKey struct {
333 Basename string `json:"basename,omitempty"`
334 Key []byte `json:"key,omitempty"`
335 Private []byte `json:"private,omitempty"`
336 DS []byte `json:"ds,omitempty"`
337}
338
339func newDNSSecKey(zone string) (DNSSecKey, error) {
340 key := &dns.DNSKEY{
341 Hdr: dns.RR_Header{Name: dns.Fqdn(zone), Class: dns.ClassINET, Ttl: 3600, Rrtype: dns.TypeDNSKEY},
342 Algorithm: dns.ECDSAP256SHA256, Flags: 257, Protocol: 3,
343 }
344 priv, err := key.Generate(256)
345 if err != nil {
346 return DNSSecKey{}, err
347 }
348 return DNSSecKey{
349 Basename: fmt.Sprintf("K%s+%03d+%05d", key.Header().Name, key.Algorithm, key.KeyTag()),
350 Key: []byte(key.String()),
351 Private: []byte(key.PrivateKeyString(priv)),
352 DS: []byte(key.ToDS(dns.SHA256).String()),
353 }, nil
354}