blob: 0216f84a9948b4fcc5900bd244b3a504efe81926 [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"
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +04007 "strings"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +04008
9 "github.com/miekg/dns"
10
11 "github.com/giolekva/pcloud/core/installer"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040012)
13
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +040014var initGroups = []string{"admin"}
15
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040016func SetupInfra(env Env, startIP net.IP, st *state) []Task {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040017 t := newLeafTask("Create client", func() error {
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040018 repo, err := st.ssClient.GetRepo("config")
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040019 if err != nil {
20 return err
21 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040022 r := installer.NewRepoIO(repo, st.ssClient.Signer)
23 appManager, err := installer.NewAppManager(r, st.nsCreator)
24 if err != nil {
25 return err
26 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040027 st.appManager = appManager
28 st.appsRepo = installer.NewInMemoryAppRepository(installer.CreateAllApps())
29 st.nsGen = installer.NewPrefixGenerator(env.Name + "-")
30 st.emptySuffixGen = installer.NewEmptySuffixGenerator()
31 return nil
32 })
33 return []Task{
34 CommitEnvironmentConfiguration(env, st),
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +040035 ConfigureFirstAccount(env, st),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040036 &t,
37 newConcurrentParentTask(
38 "Core services",
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040039 SetupNetwork(env, startIP, st),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040040 SetupCertificateIssuers(env, st),
41 SetupAuth(env, st),
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040042 SetupGroupMemberships(env, st),
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040043 SetupHeadscale(env, startIP, st),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040044 SetupWelcome(env, st),
45 SetupAppStore(env, st),
46 ),
47 }
48}
49
50func CommitEnvironmentConfiguration(env Env, st *state) Task {
51 t := newLeafTask("Configure environment infrastructure", func() error {
52 repo, err := st.ssClient.GetRepo("config")
53 if err != nil {
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040054 return err
55 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040056 r := installer.NewRepoIO(repo, st.ssClient.Signer)
57 {
58 // TODO(giolekva): private domain can be configurable as well
59 config := installer.Config{
60 Values: installer.Values{
61 PCloudEnvName: env.PCloudEnvName,
62 Id: env.Name,
63 ContactEmail: env.ContactEmail,
64 Domain: env.Domain,
65 PrivateDomain: fmt.Sprintf("p.%s", env.Domain),
66 PublicIP: st.publicIPs[0].String(),
67 NamespacePrefix: fmt.Sprintf("%s-", env.Name),
68 },
69 }
70 if err := r.WriteYaml("config.yaml", config); err != nil {
71 return err
72 }
73 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040074 {
75 out, err := r.Writer("pcloud-charts.yaml")
76 if err != nil {
77 return err
78 }
79 defer out.Close()
80 _, err = fmt.Fprintf(out, `
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040081apiVersion: source.toolkit.fluxcd.io/v1
82kind: GitRepository
83metadata:
84 name: pcloud
85 namespace: %s
86spec:
87 interval: 1m0s
88 url: https://github.com/giolekva/pcloud
89 ref:
Giorgi Lekveishvili024757c2024-03-14 13:27:29 +040090 branch: main
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040091`, env.Name)
92 if err != nil {
93 return err
94 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040095 rootKust, err := r.ReadKustomization("kustomization.yaml")
96 if err != nil {
97 return err
98 }
99 rootKust.AddResources("pcloud-charts.yaml")
100 if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
101 return err
102 }
103 r.CommitAndPush("configure charts repo")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400104 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400105 return nil
106 })
107 return &t
108}
109
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400110type firstAccount struct {
111 Created bool `json:"created"`
112 Groups []string `json:"groups"`
113}
114
115func ConfigureFirstAccount(env Env, st *state) Task {
116 t := newLeafTask("Configure first account settings", func() error {
117 repo, err := st.ssClient.GetRepo("config")
118 if err != nil {
119 return err
120 }
121 r := installer.NewRepoIO(repo, st.ssClient.Signer)
122 fa := firstAccount{false, initGroups}
123 if err := r.WriteYaml("first-account.yaml", fa); err != nil {
124 return err
125 }
126 return r.CommitAndPush("first account membership configuration")
127 })
128 return &t
129}
130
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400131func SetupNetwork(env Env, startIP net.IP, st *state) Task {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400132 t := newLeafTask("Setup network", func() error {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400133 startAddr, err := netip.ParseAddr(startIP.String())
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400134 if err != nil {
135 return err
136 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400137 if !startAddr.Is4() {
138 return fmt.Errorf("Expected IPv4, got %s instead", startAddr)
139 }
140 addr := startAddr.AsSlice()
141 if addr[3] != 0 {
142 return fmt.Errorf("Expected last byte to be zero, got %d instead", addr[3])
143 }
144 addr[3] = 10
145 fromIP, ok := netip.AddrFromSlice(addr)
146 if !ok {
147 return fmt.Errorf("Must not reach")
148 }
149 addr[3] = 254
150 toIP, ok := netip.AddrFromSlice(addr)
151 if !ok {
152 return fmt.Errorf("Must not reach")
153 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400154 {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400155 ingressPrivateIP := startAddr
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400156 headscaleIP := ingressPrivateIP.Next()
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400157 app, err := st.appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400158 if err != nil {
159 return err
160 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400161 if err := st.appManager.Install(app, st.nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400162 "name": fmt.Sprintf("%s-ingress-private", env.Name),
163 "from": ingressPrivateIP.String(),
164 "to": ingressPrivateIP.String(),
165 "autoAssign": false,
166 "namespace": "metallb-system",
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400167 }); err != nil {
168 return err
169 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400170 if err := st.appManager.Install(app, st.nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400171 "name": fmt.Sprintf("%s-headscale", env.Name),
172 "from": headscaleIP.String(),
173 "to": headscaleIP.String(),
174 "autoAssign": false,
175 "namespace": "metallb-system",
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400176 }); err != nil {
177 return err
178 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400179 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400180 "name": env.Name,
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400181 "from": fromIP.String(),
182 "to": toIP.String(),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400183 "autoAssign": false,
184 "namespace": "metallb-system",
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400185 }); err != nil {
186 return err
187 }
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400188 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400189 {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400190 app, err := st.appsRepo.Find("private-network")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400191 if err != nil {
192 return err
193 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400194 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400195 "privateNetwork": map[string]any{
196 "hostname": "private-network-proxy",
197 "username": "private-network-proxy",
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400198 "ipSubnet": fmt.Sprintf("%s/24", startIP.String()),
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400199 },
200 }); err != nil {
201 return err
202 }
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400203 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400204 return nil
205 })
206 return &t
207}
208
209func SetupCertificateIssuers(env Env, st *state) Task {
210 pub := newLeafTask(fmt.Sprintf("Public %s", env.Domain), func() error {
211 app, err := st.appsRepo.Find("certificate-issuer-public")
212 if err != nil {
213 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400214 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400215 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400216 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400217 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400218 return nil
219 })
220 priv := newLeafTask(fmt.Sprintf("Private p.%s", env.Domain), func() error {
221 app, err := st.appsRepo.Find("certificate-issuer-private")
222 if err != nil {
223 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400224 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400225 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400226 "apiConfigMap": map[string]any{
227 "name": "api-config", // TODO(gio): take from global pcloud config
228 "namespace": fmt.Sprintf("%s-dns-zone-manager", env.PCloudEnvName),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400229 },
230 }); err != nil {
231 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400232 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400233 return nil
234 })
235 return newSequentialParentTask("Configure TLS certificate issuers", &pub, &priv)
236}
237
238func SetupAuth(env Env, st *state) Task {
239 t := newLeafTask("Setup", func() error {
240 app, err := st.appsRepo.Find("core-auth")
241 if err != nil {
242 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400243 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400244 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400245 "subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400246 }); err != nil {
247 return err
248 }
249 return nil
250 })
251 return newSequentialParentTask(
252 "Authentication services",
253 &t,
254 waitForAddr(fmt.Sprintf("https://accounts-ui.%s", env.Domain)),
255 )
256}
257
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400258func SetupGroupMemberships(env Env, st *state) Task {
259 t := newLeafTask("Setup", func() error {
260 app, err := st.appsRepo.Find("memberships")
261 if err != nil {
262 return err
263 }
Giorgi Lekveishvilid542b732024-03-25 18:17:39 +0400264 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
265 "authGroups": strings.Join(initGroups, ","),
266 }); err != nil {
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400267 return err
268 }
269 return nil
270 })
271 return newSequentialParentTask(
272 "Group Membership",
273 &t,
274 waitForAddr(fmt.Sprintf("https://memberships.p.%s", env.Domain)),
275 )
276}
277
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400278func SetupHeadscale(env Env, startIP net.IP, st *state) Task {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400279 t := newLeafTask("Setup", func() error {
280 app, err := st.appsRepo.Find("headscale")
281 if err != nil {
282 return err
283 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400284 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400285 "subdomain": "headscale",
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400286 "ipSubnet": fmt.Sprintf("%s/24", startIP),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400287 }); err != nil {
288 return err
289 }
290 return nil
291 })
292 return newSequentialParentTask(
293 "Headscale service",
294 &t,
295 waitForAddr(fmt.Sprintf("https://headscale.%s/apple", env.Domain)),
296 )
297}
298
299func SetupWelcome(env Env, st *state) Task {
300 t := newLeafTask("Setup", func() error {
301 keys, err := installer.NewSSHKeyPair("welcome")
302 if err != nil {
303 return err
304 }
305 user := fmt.Sprintf("%s-welcome", env.Name)
306 if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
307 return err
308 }
309 if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
310 return err
311 }
312 app, err := st.appsRepo.Find("welcome")
313 if err != nil {
314 return err
315 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400316 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400317 "repoAddr": st.ssClient.GetRepoAddress("config"),
318 "sshPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400319 }); err != nil {
320 return err
321 }
322 return nil
323 })
324 return newSequentialParentTask(
325 "Welcome service",
326 &t,
327 waitForAddr(fmt.Sprintf("https://welcome.%s", env.Domain)),
328 )
329}
330
331func SetupAppStore(env Env, st *state) Task {
332 t := newLeafTask("Application marketplace", func() error {
333 user := fmt.Sprintf("%s-appmanager", env.Name)
334 keys, err := installer.NewSSHKeyPair(user)
335 if err != nil {
336 return err
337 }
338 if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
339 return err
340 }
341 if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
342 return err
343 }
344 app, err := st.appsRepo.Find("app-manager") // TODO(giolekva): configure
345 if err != nil {
346 return err
347 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400348 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400349 "repoAddr": st.ssClient.GetRepoAddress("config"),
350 "sshPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili3c91e8b2024-03-25 20:20:14 +0400351 "authGroups": strings.Join(initGroups, ","),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400352 }); err != nil {
353 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400354 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400355 return nil
356 })
357 return &t
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400358}
359
360type DNSSecKey struct {
361 Basename string `json:"basename,omitempty"`
362 Key []byte `json:"key,omitempty"`
363 Private []byte `json:"private,omitempty"`
364 DS []byte `json:"ds,omitempty"`
365}
366
367func newDNSSecKey(zone string) (DNSSecKey, error) {
368 key := &dns.DNSKEY{
369 Hdr: dns.RR_Header{Name: dns.Fqdn(zone), Class: dns.ClassINET, Ttl: 3600, Rrtype: dns.TypeDNSKEY},
370 Algorithm: dns.ECDSAP256SHA256, Flags: 257, Protocol: 3,
371 }
372 priv, err := key.Generate(256)
373 if err != nil {
374 return DNSSecKey{}, err
375 }
376 return DNSSecKey{
377 Basename: fmt.Sprintf("K%s+%03d+%05d", key.Header().Name, key.Algorithm, key.KeyTag()),
378 Key: []byte(key.String()),
379 Private: []byte(key.PrivateKeyString(priv)),
380 DS: []byte(key.ToDS(dns.SHA256).String()),
381 }, nil
382}