blob: 995744c74e3293eed50099bf4a4e73167a25b0dc [file] [log] [blame]
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +04001package tasks
2
3import (
4 "fmt"
5 "net/netip"
6
7 "github.com/miekg/dns"
8
9 "github.com/giolekva/pcloud/core/installer"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040010)
11
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040012func SetupInfra(env Env, st *state) []Task {
13 t := newLeafTask("Create client", func() error {
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040014 repo, err := st.ssClient.GetRepo("config")
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040015 if err != nil {
16 return err
17 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040018 r := installer.NewRepoIO(repo, st.ssClient.Signer)
19 appManager, err := installer.NewAppManager(r, st.nsCreator)
20 if err != nil {
21 return err
22 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040023 st.appManager = appManager
24 st.appsRepo = installer.NewInMemoryAppRepository(installer.CreateAllApps())
25 st.nsGen = installer.NewPrefixGenerator(env.Name + "-")
26 st.emptySuffixGen = installer.NewEmptySuffixGenerator()
27 return nil
28 })
29 return []Task{
30 CommitEnvironmentConfiguration(env, st),
31 &t,
32 newConcurrentParentTask(
33 "Core services",
34 SetupNetwork(env, st),
35 SetupCertificateIssuers(env, st),
36 SetupAuth(env, st),
37 SetupHeadscale(env, st),
38 SetupWelcome(env, st),
39 SetupAppStore(env, st),
40 ),
41 }
42}
43
44func CommitEnvironmentConfiguration(env Env, st *state) Task {
45 t := newLeafTask("Configure environment infrastructure", func() error {
46 repo, err := st.ssClient.GetRepo("config")
47 if err != nil {
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040048 return err
49 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040050 r := installer.NewRepoIO(repo, st.ssClient.Signer)
51 {
52 // TODO(giolekva): private domain can be configurable as well
53 config := installer.Config{
54 Values: installer.Values{
55 PCloudEnvName: env.PCloudEnvName,
56 Id: env.Name,
57 ContactEmail: env.ContactEmail,
58 Domain: env.Domain,
59 PrivateDomain: fmt.Sprintf("p.%s", env.Domain),
60 PublicIP: st.publicIPs[0].String(),
61 NamespacePrefix: fmt.Sprintf("%s-", env.Name),
62 },
63 }
64 if err := r.WriteYaml("config.yaml", config); err != nil {
65 return err
66 }
67 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040068 {
69 out, err := r.Writer("pcloud-charts.yaml")
70 if err != nil {
71 return err
72 }
73 defer out.Close()
74 _, err = fmt.Fprintf(out, `
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040075apiVersion: source.toolkit.fluxcd.io/v1
76kind: GitRepository
77metadata:
78 name: pcloud
79 namespace: %s
80spec:
81 interval: 1m0s
82 url: https://github.com/giolekva/pcloud
83 ref:
84 branch: main
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040085`, env.Name)
86 if err != nil {
87 return err
88 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040089 rootKust, err := r.ReadKustomization("kustomization.yaml")
90 if err != nil {
91 return err
92 }
93 rootKust.AddResources("pcloud-charts.yaml")
94 if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
95 return err
96 }
97 r.CommitAndPush("configure charts repo")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +040098 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040099 return nil
100 })
101 return &t
102}
103
104func SetupNetwork(env Env, st *state) Task {
105 t := newLeafTask("Setup network", func() error {
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400106 ingressPrivateIP, err := netip.ParseAddr("10.1.0.1")
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400107 if err != nil {
108 return err
109 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400110 {
111 headscaleIP := ingressPrivateIP.Next()
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400112 app, err := st.appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400113 if err != nil {
114 return err
115 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400116 if err := st.appManager.Install(*app, st.nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400117 "Name": fmt.Sprintf("%s-ingress-private", env.Name),
118 "From": ingressPrivateIP.String(),
119 "To": ingressPrivateIP.String(),
120 "AutoAssign": false,
121 "Namespace": "metallb-system",
122 }); err != nil {
123 return err
124 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400125 if err := st.appManager.Install(*app, st.nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400126 "Name": fmt.Sprintf("%s-headscale", env.Name),
127 "From": headscaleIP.String(),
128 "To": headscaleIP.String(),
129 "AutoAssign": false,
130 "Namespace": "metallb-system",
131 }); err != nil {
132 return err
133 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400134 if err := st.appManager.Install(*app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400135 "Name": env.Name,
136 "From": "10.1.0.100", // TODO(gio): auto-generate
137 "To": "10.1.0.254",
138 "AutoAssign": false,
139 "Namespace": "metallb-system",
140 }); err != nil {
141 return err
142 }
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400143 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400144 {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400145 app, err := st.appsRepo.Find("private-network")
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400146 if err != nil {
147 return err
148 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400149 if err := st.appManager.Install(*app, st.nsGen, st.emptySuffixGen, map[string]any{
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400150 "PrivateNetwork": map[string]any{
151 "Hostname": "private-network-proxy",
152 "Username": "private-network-proxy",
153 "IPSubnet": "10.1.0.0/24",
154 },
155 }); err != nil {
156 return err
157 }
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400158 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400159 return nil
160 })
161 return &t
162}
163
164func SetupCertificateIssuers(env Env, st *state) Task {
165 pub := newLeafTask(fmt.Sprintf("Public %s", env.Domain), func() error {
166 app, err := st.appsRepo.Find("certificate-issuer-public")
167 if err != nil {
168 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400169 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400170 if err := st.appManager.Install(*app, st.nsGen, st.emptySuffixGen, map[string]any{}); err != nil {
171 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400172 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400173 return nil
174 })
175 priv := newLeafTask(fmt.Sprintf("Private p.%s", env.Domain), func() error {
176 app, err := st.appsRepo.Find("certificate-issuer-private")
177 if err != nil {
178 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400179 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400180 if err := st.appManager.Install(*app, st.nsGen, st.emptySuffixGen, map[string]any{
181 "APIConfigMap": map[string]any{
182 "Name": "api-config", // TODO(gio): take from global pcloud config
183 "Namespace": fmt.Sprintf("%s-dns-zone-manager", env.PCloudEnvName),
184 },
185 }); err != nil {
186 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400187 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400188 return nil
189 })
190 return newSequentialParentTask("Configure TLS certificate issuers", &pub, &priv)
191}
192
193func SetupAuth(env Env, st *state) Task {
194 t := newLeafTask("Setup", func() error {
195 app, err := st.appsRepo.Find("core-auth")
196 if err != nil {
197 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400198 }
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400199 if err := st.appManager.Install(*app, st.nsGen, st.emptySuffixGen, map[string]any{
200 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
201 }); err != nil {
202 return err
203 }
204 return nil
205 })
206 return newSequentialParentTask(
207 "Authentication services",
208 &t,
209 waitForAddr(fmt.Sprintf("https://accounts-ui.%s", env.Domain)),
210 )
211}
212
213func SetupHeadscale(env Env, st *state) Task {
214 t := newLeafTask("Setup", func() error {
215 app, err := st.appsRepo.Find("headscale")
216 if err != nil {
217 return err
218 }
219 if err := st.appManager.Install(*app, st.nsGen, st.emptySuffixGen, map[string]any{
220 "Subdomain": "headscale",
221 }); err != nil {
222 return err
223 }
224 return nil
225 })
226 return newSequentialParentTask(
227 "Headscale service",
228 &t,
229 waitForAddr(fmt.Sprintf("https://headscale.%s/apple", env.Domain)),
230 )
231}
232
233func SetupWelcome(env Env, st *state) Task {
234 t := newLeafTask("Setup", func() error {
235 keys, err := installer.NewSSHKeyPair("welcome")
236 if err != nil {
237 return err
238 }
239 user := fmt.Sprintf("%s-welcome", env.Name)
240 if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
241 return err
242 }
243 if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
244 return err
245 }
246 app, err := st.appsRepo.Find("welcome")
247 if err != nil {
248 return err
249 }
250 if err := st.appManager.Install(*app, st.nsGen, st.emptySuffixGen, map[string]any{
251 "RepoAddr": st.ssClient.GetRepoAddress("config"),
252 "SSHPrivateKey": string(keys.RawPrivateKey()),
253 }); err != nil {
254 return err
255 }
256 return nil
257 })
258 return newSequentialParentTask(
259 "Welcome service",
260 &t,
261 waitForAddr(fmt.Sprintf("https://welcome.%s", env.Domain)),
262 )
263}
264
265func SetupAppStore(env Env, st *state) Task {
266 t := newLeafTask("Application marketplace", func() error {
267 user := fmt.Sprintf("%s-appmanager", env.Name)
268 keys, err := installer.NewSSHKeyPair(user)
269 if err != nil {
270 return err
271 }
272 if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
273 return err
274 }
275 if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
276 return err
277 }
278 app, err := st.appsRepo.Find("app-manager") // TODO(giolekva): configure
279 if err != nil {
280 return err
281 }
282 if err := st.appManager.Install(*app, st.nsGen, st.emptySuffixGen, map[string]any{
283 "RepoAddr": st.ssClient.GetRepoAddress("config"),
284 "SSHPrivateKey": string(keys.RawPrivateKey()),
285 }); err != nil {
286 return err
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400287 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400288 return nil
289 })
290 return &t
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400291}
292
293type DNSSecKey struct {
294 Basename string `json:"basename,omitempty"`
295 Key []byte `json:"key,omitempty"`
296 Private []byte `json:"private,omitempty"`
297 DS []byte `json:"ds,omitempty"`
298}
299
300func newDNSSecKey(zone string) (DNSSecKey, error) {
301 key := &dns.DNSKEY{
302 Hdr: dns.RR_Header{Name: dns.Fqdn(zone), Class: dns.ClassINET, Ttl: 3600, Rrtype: dns.TypeDNSKEY},
303 Algorithm: dns.ECDSAP256SHA256, Flags: 257, Protocol: 3,
304 }
305 priv, err := key.Generate(256)
306 if err != nil {
307 return DNSSecKey{}, err
308 }
309 return DNSSecKey{
310 Basename: fmt.Sprintf("K%s+%03d+%05d", key.Header().Name, key.Algorithm, key.KeyTag()),
311 Key: []byte(key.String()),
312 Private: []byte(key.PrivateKeyString(priv)),
313 DS: []byte(key.ToDS(dns.SHA256).String()),
314 }, nil
315}