carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "net"
6 "net/http"
7 "sort"
8 "strings"
9
10 "github.com/carlosframework/platform/internal/geo"
11 "github.com/carlosframework/platform/internal/provision"
12)
13
14// regionReq is what a create door hands claimApp: never a resolved region.
15// The guess is resolved INSIDE claimApp, after it knows whether this is an
16// adoption (spec §5.5). ip is request-local and only set for a person. The
17// timezone and the address go into the guess and nowhere else: never
18// logged, stored, audited or echoed (spec §6.5).
19type regionReq struct {
20 explicit string // a person's choice, record vocabulary
21 hasExplicit bool
22 tz string
23 ip net.IP // request-local, person requests only
24}
25
26// validateExplicitRegion checks a person's explicit region as the instance
27// door checks one (spec §8.1): ValidRegion; armed and hosting unless the
28// gate is dormant; not provably without the secrets key (R9). The home's
29// name normalizes to "". A refusal lists exactly the codes this same check
30// would accept now (codex plan-R4 2).
31func (s *Server) validateExplicitRegion(ctx context.Context, raw string) (string, *doorRefusal) {
32 code := s.homeRegionIn(strings.TrimSpace(raw))
33 if code == "" {
34 return "", nil
35 }
36 gate := s.readExplicitGate(ctx)
37 switch gate.refusal(code) {
38 case "":
39 return code, nil
40 case "unknown":
41 return "", &doorRefusal{http.StatusBadRequest, msgRegionUnknown(code, s.explicitChoices(gate))}
42 case "not-armed":
43 return "", &doorRefusal{http.StatusBadRequest, msgRegionNotArmed(code, s.explicitChoices(gate))}
44 }
45 return "", &doorRefusal{http.StatusBadRequest, msgNoSecretsKey(code)}
46}
47
48// explicitGate is one read of what an explicit app region must pass.
49type explicitGate struct {
50 caps map[string]placementCapability
51 published bool
52 cands []regionCandidate // nil when the edges read failed: R9 cannot refuse
53}
54
55func (s *Server) readExplicitGate(ctx context.Context) explicitGate {
56 g := explicitGate{}
57 g.caps, g.published = placements(ctx, s.cfg.Releases)
58 if snap, err := s.liveEdges(ctx); err == nil {
59 g.cands = s.regionCands(snap)
60 }
61 return g
62}
63
64// refusal is "" when an explicit, record-vocabulary code passes, else
65// which bar it fails: "unknown", "not-armed" or "no-key".
66func (g explicitGate) refusal(code string) string {
67 switch {
68 case code == "":
69 return ""
70 case !provision.ValidRegion(code):
71 return "unknown"
72 case g.published && !(g.caps[code].Reachable && g.caps[code].Hosting):
73 return "not-armed"
74 }
75 if c, ok := findCandidate(g.cands, code); ok && c.NoKeyProven {
76 return "no-key"
77 }
78 return ""
79}
80
81// explicitChoices is the home's name plus every published code that
82// refusal passes, sorted: never a code this door would refuse.
83func (s *Server) explicitChoices(g explicitGate) []string {
84 set := map[string]bool{}
85 if s.cfg.HomeRegion != "" {
86 set[s.cfg.HomeRegion] = true
87 }
88 for code := range g.caps {
89 if c := s.homeRegionIn(code); c != "" && g.refusal(c) == "" {
90 set[c] = true
91 }
92 }
93 out := make([]string, 0, len(set))
94 for c := range set {
95 out = append(out, c)
96 }
97 sort.Strings(out)
98 return out
99}
100
101// resolveNewAppRegion is spec §5.5 and §5.6 for a NEW row. A guess must
102// pass the same predicate that builds the C18 and C19 lists
103// (explicitGate.refusal), so no door ever stores a region another door
104// would refuse to list.
105func (s *Server) resolveNewAppRegion(ctx context.Context, rq regionReq, adopting bool) (region, source string) {
106 if !s.appRegionsOn() {
107 return "", "default"
108 }
109 if rq.hasExplicit {
110 // Defence in depth for a future door that forgets to validate: a
111 // code that is not a region never reaches the row. "" is the home.
112 if rq.explicit != "" && !provision.ValidRegion(rq.explicit) {
113 return "", "default"
114 }
115 return rq.explicit, "chosen"
116 }
117 if adopting {
118 return "", "default"
119 }
120 g := s.readExplicitGate(ctx)
121 if g.cands == nil {
122 return "", "default"
123 }
124 guess, ok := guessRegion(g.cands, func(code string) bool { return g.refusal(code) == "" },
125 guessInput{TZ: rq.tz, IP: rq.ip}, s.cfg.GeoIP)
126 if !ok {
127 return "", "default"
128 }
129 return guess.Code, guess.Source
130}
131
132// placeOf names a record-vocabulary code: its AWS region's place, else the
133// code under its name.
134func (s *Server) placeOf(code string, cands []regionCandidate) string {
135 c, _ := findCandidate(cands, code)
136 return geo.PlaceName(c.AWSRegion, s.homeRegionOut(code))
137}
138
139// personIP is the request's own address, for a person only (spec §6.2).
140// A service or app principal is a server; its address says nothing about
141// where a person is.
142func (s *Server) personIP(r *http.Request) net.IP {
143 if p, ok := principalFrom(r.Context()); ok && p.Kind != PrincipalUser {
144 return nil
145 }
146 return net.ParseIP(s.clientIP(r))
147}
148