blob: 59bc986f4fd6bc3b04d3d54c8a4a591ce9a3ca5e [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 Lekveishvili9d5e3f52024-03-13 15:02:50 +040038 SetupHeadscale(env, startIP, st),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040039 SetupWelcome(env, st),
40 SetupAppStore(env, st),
41 ),
42 }
43}
44
45func CommitEnvironmentConfiguration(env Env, st *state) Task {
46 t := newLeafTask("Configure environment infrastructure", func() error {
47 repo, err := st.ssClient.GetRepo("config")
48 if err != nil {
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040049 return err
50 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040051 r := installer.NewRepoIO(repo, st.ssClient.Signer)
52 {
53 // TODO(giolekva): private domain can be configurable as well
54 config := installer.Config{
55 Values: installer.Values{
56 PCloudEnvName: env.PCloudEnvName,
57 Id: env.Name,
58 ContactEmail: env.ContactEmail,
59 Domain: env.Domain,
60 PrivateDomain: fmt.Sprintf("p.%s", env.Domain),
61 PublicIP: st.publicIPs[0].String(),
62 NamespacePrefix: fmt.Sprintf("%s-", env.Name),
63 },
64 }
65 if err := r.WriteYaml("config.yaml", config); err != nil {
66 return err
67 }
68 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040069 {
70 out, err := r.Writer("pcloud-charts.yaml")
71 if err != nil {
72 return err
73 }
74 defer out.Close()
75 _, err = fmt.Fprintf(out, `
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040076apiVersion: source.toolkit.fluxcd.io/v1
77kind: GitRepository
78metadata:
79 name: pcloud
80 namespace: %s
81spec:
82 interval: 1m0s
83 url: https://github.com/giolekva/pcloud
84 ref:
Giorgi Lekveishvili024757c2024-03-14 13:27:29 +040085 branch: main
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040086`, env.Name)
87 if err != nil {
88 return err
89 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040090 rootKust, err := r.ReadKustomization("kustomization.yaml")
91 if err != nil {
92 return err
93 }
94 rootKust.AddResources("pcloud-charts.yaml")
95 if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
96 return err
97 }
98 r.CommitAndPush("configure charts repo")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040099 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400100 return nil
101 })
102 return &t
103}
104
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400105func SetupNetwork(env Env, startIP net.IP, st *state) Task {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400106 t := newLeafTask("Setup network", func() error {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400107 startAddr, err := netip.ParseAddr(startIP.String())
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400108 if err != nil {
109 return err
110 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400111 if !startAddr.Is4() {
112 return fmt.Errorf("Expected IPv4, got %s instead", startAddr)
113 }
114 addr := startAddr.AsSlice()
115 if addr[3] != 0 {
116 return fmt.Errorf("Expected last byte to be zero, got %d instead", addr[3])
117 }
118 addr[3] = 10
119 fromIP, ok := netip.AddrFromSlice(addr)
120 if !ok {
121 return fmt.Errorf("Must not reach")
122 }
123 addr[3] = 254
124 toIP, ok := netip.AddrFromSlice(addr)
125 if !ok {
126 return fmt.Errorf("Must not reach")
127 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400128 {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400129 ingressPrivateIP := startAddr
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400130 headscaleIP := ingressPrivateIP.Next()
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400131 app, err := st.appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400132 if err != nil {
133 return err
134 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400135 if err := st.appManager.Install(app, st.nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400136 "name": fmt.Sprintf("%s-ingress-private", env.Name),
137 "from": ingressPrivateIP.String(),
138 "to": ingressPrivateIP.String(),
139 "autoAssign": false,
140 "namespace": "metallb-system",
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400141 }); err != nil {
142 return err
143 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400144 if err := st.appManager.Install(app, st.nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400145 "name": fmt.Sprintf("%s-headscale", env.Name),
146 "from": headscaleIP.String(),
147 "to": headscaleIP.String(),
148 "autoAssign": false,
149 "namespace": "metallb-system",
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400150 }); err != nil {
151 return err
152 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400153 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400154 "name": env.Name,
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400155 "from": fromIP.String(),
156 "to": toIP.String(),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400157 "autoAssign": false,
158 "namespace": "metallb-system",
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400159 }); err != nil {
160 return err
161 }
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400162 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400163 {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400164 app, err := st.appsRepo.Find("private-network")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400165 if err != nil {
166 return err
167 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400168 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400169 "privateNetwork": map[string]any{
170 "hostname": "private-network-proxy",
171 "username": "private-network-proxy",
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400172 "ipSubnet": fmt.Sprintf("%s/24", startIP.String()),
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400173 },
174 }); err != nil {
175 return err
176 }
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400177 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400178 return nil
179 })
180 return &t
181}
182
183func SetupCertificateIssuers(env Env, st *state) Task {
184 pub := newLeafTask(fmt.Sprintf("Public %s", env.Domain), func() error {
185 app, err := st.appsRepo.Find("certificate-issuer-public")
186 if err != nil {
187 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400188 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400189 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400190 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400191 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400192 return nil
193 })
194 priv := newLeafTask(fmt.Sprintf("Private p.%s", env.Domain), func() error {
195 app, err := st.appsRepo.Find("certificate-issuer-private")
196 if err != nil {
197 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400198 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400199 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400200 "apiConfigMap": map[string]any{
201 "name": "api-config", // TODO(gio): take from global pcloud config
202 "namespace": fmt.Sprintf("%s-dns-zone-manager", env.PCloudEnvName),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400203 },
204 }); err != nil {
205 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400206 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400207 return nil
208 })
209 return newSequentialParentTask("Configure TLS certificate issuers", &pub, &priv)
210}
211
212func SetupAuth(env Env, st *state) Task {
213 t := newLeafTask("Setup", func() error {
214 app, err := st.appsRepo.Find("core-auth")
215 if err != nil {
216 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400217 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400218 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400219 "subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400220 }); err != nil {
221 return err
222 }
223 return nil
224 })
225 return newSequentialParentTask(
226 "Authentication services",
227 &t,
228 waitForAddr(fmt.Sprintf("https://accounts-ui.%s", env.Domain)),
229 )
230}
231
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400232func SetupHeadscale(env Env, startIP net.IP, st *state) Task {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400233 t := newLeafTask("Setup", func() error {
234 app, err := st.appsRepo.Find("headscale")
235 if err != nil {
236 return err
237 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400238 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400239 "subdomain": "headscale",
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400240 "ipSubnet": fmt.Sprintf("%s/24", startIP),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400241 }); err != nil {
242 return err
243 }
244 return nil
245 })
246 return newSequentialParentTask(
247 "Headscale service",
248 &t,
249 waitForAddr(fmt.Sprintf("https://headscale.%s/apple", env.Domain)),
250 )
251}
252
253func SetupWelcome(env Env, st *state) Task {
254 t := newLeafTask("Setup", func() error {
255 keys, err := installer.NewSSHKeyPair("welcome")
256 if err != nil {
257 return err
258 }
259 user := fmt.Sprintf("%s-welcome", env.Name)
260 if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
261 return err
262 }
263 if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
264 return err
265 }
266 app, err := st.appsRepo.Find("welcome")
267 if err != nil {
268 return err
269 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400270 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400271 "repoAddr": st.ssClient.GetRepoAddress("config"),
272 "sshPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400273 }); err != nil {
274 return err
275 }
276 return nil
277 })
278 return newSequentialParentTask(
279 "Welcome service",
280 &t,
281 waitForAddr(fmt.Sprintf("https://welcome.%s", env.Domain)),
282 )
283}
284
285func SetupAppStore(env Env, st *state) Task {
286 t := newLeafTask("Application marketplace", func() error {
287 user := fmt.Sprintf("%s-appmanager", env.Name)
288 keys, err := installer.NewSSHKeyPair(user)
289 if err != nil {
290 return err
291 }
292 if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
293 return err
294 }
295 if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
296 return err
297 }
298 app, err := st.appsRepo.Find("app-manager") // TODO(giolekva): configure
299 if err != nil {
300 return err
301 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400302 if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400303 "repoAddr": st.ssClient.GetRepoAddress("config"),
304 "sshPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400305 }); err != nil {
306 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400307 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400308 return nil
309 })
310 return &t
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400311}
312
313type DNSSecKey struct {
314 Basename string `json:"basename,omitempty"`
315 Key []byte `json:"key,omitempty"`
316 Private []byte `json:"private,omitempty"`
317 DS []byte `json:"ds,omitempty"`
318}
319
320func newDNSSecKey(zone string) (DNSSecKey, error) {
321 key := &dns.DNSKEY{
322 Hdr: dns.RR_Header{Name: dns.Fqdn(zone), Class: dns.ClassINET, Ttl: 3600, Rrtype: dns.TypeDNSKEY},
323 Algorithm: dns.ECDSAP256SHA256, Flags: 257, Protocol: 3,
324 }
325 priv, err := key.Generate(256)
326 if err != nil {
327 return DNSSecKey{}, err
328 }
329 return DNSSecKey{
330 Basename: fmt.Sprintf("K%s+%03d+%05d", key.Header().Name, key.Algorithm, key.KeyTag()),
331 Key: []byte(key.String()),
332 Private: []byte(key.PrivateKeyString(priv)),
333 DS: []byte(key.ToDS(dns.SHA256).String()),
334 }, nil
335}