forked from
tangled.org/core
Monorepo for Tangled
4.5 kB
203 lines
1package serververify
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "net"
8 "net/http"
9 "syscall"
10 "time"
11
12 indigoxrpc "github.com/bluesky-social/indigo/xrpc"
13 "tangled.org/core/api/tangled"
14 "tangled.org/core/appview/db"
15 "tangled.org/core/orm"
16 "tangled.org/core/rbac"
17 "tangled.org/core/xrpc/xrpcclient"
18)
19
20var (
21 FetchError = errors.New("failed to fetch owner")
22)
23
24const verifyTimeout = 10 * time.Second
25
26// fetchOwner fetches the owner DID from a server's /owner endpoint
27func fetchOwner(ctx context.Context, domain string, dev bool) (string, error) {
28 scheme := "https"
29 if dev {
30 scheme = "http"
31 }
32
33 host := fmt.Sprintf("%s://%s", scheme, domain)
34 transport := &http.Transport{
35 DialContext: safeDialer(dev).DialContext,
36 }
37 xrpcc := &indigoxrpc.Client{
38 Host: host,
39 Client: &http.Client{
40 Timeout: verifyTimeout,
41 Transport: transport,
42 },
43 }
44
45 res, err := tangled.Owner(ctx, xrpcc)
46 if err != nil {
47 var xrpcerr *indigoxrpc.Error
48 if !errors.As(err, &xrpcerr) {
49 return "", err
50 }
51 if handled := xrpcclient.HandleXrpcErr(err); handled != nil {
52 return "", handled
53 }
54 }
55
56 return res.Owner, nil
57}
58
59type OwnerMismatch struct {
60 expected string
61 observed string
62}
63
64func (e *OwnerMismatch) Error() string {
65 return fmt.Sprintf("owner mismatch: %q != %q", e.expected, e.observed)
66}
67
68// RunVerification verifies that the server at the given domain has the expected owner
69func RunVerification(ctx context.Context, domain, expectedOwner string, dev bool) error {
70 observedOwner, err := fetchOwner(ctx, domain, dev)
71 if err != nil {
72 return err
73 }
74
75 if observedOwner != expectedOwner {
76 return &OwnerMismatch{
77 expected: expectedOwner,
78 observed: observedOwner,
79 }
80 }
81
82 return nil
83}
84
85// MarkSpindleVerified marks a spindle as verified in the DB and adds the user as its owner
86func MarkSpindleVerified(d *db.DB, e *rbac.Enforcer, instance, owner string) (int64, error) {
87 tx, err := d.Begin()
88 if err != nil {
89 return 0, fmt.Errorf("failed to create txn: %w", err)
90 }
91 committed := false
92 defer func() {
93 if committed {
94 return
95 }
96 tx.Rollback()
97 e.E.LoadPolicy()
98 }()
99
100 // mark this spindle as verified in the db
101 rowId, err := db.VerifySpindle(
102 tx,
103 orm.FilterEq("owner", owner),
104 orm.FilterEq("instance", instance),
105 )
106 if err != nil {
107 return 0, fmt.Errorf("failed to write to DB: %w", err)
108 }
109
110 err = e.AddSpindleOwner(instance, owner)
111 if err != nil {
112 return 0, fmt.Errorf("failed to update ACL: %w", err)
113 }
114
115 err = tx.Commit()
116 if err != nil {
117 return 0, fmt.Errorf("failed to commit txn: %w", err)
118 }
119
120 err = e.E.SavePolicy()
121 if err != nil {
122 return 0, fmt.Errorf("failed to update ACL: %w", err)
123 }
124 committed = true
125
126 return rowId, nil
127}
128
129// MarkKnotVerified marks a knot as verified and sets up ownership/permissions
130func MarkKnotVerified(d *db.DB, e *rbac.Enforcer, domain, owner string) error {
131 tx, err := d.BeginTx(context.Background(), nil)
132 if err != nil {
133 return fmt.Errorf("failed to start tx: %w", err)
134 }
135 committed := false
136 defer func() {
137 if committed {
138 return
139 }
140 tx.Rollback()
141 e.E.LoadPolicy()
142 }()
143
144 // mark as registered
145 err = db.MarkRegistered(
146 tx,
147 orm.FilterEq("did", owner),
148 orm.FilterEq("domain", domain),
149 )
150 if err != nil {
151 return fmt.Errorf("failed to register domain: %w", err)
152 }
153
154 // add basic acls for this domain
155 err = e.AddKnot(domain)
156 if err != nil {
157 return fmt.Errorf("failed to add knot to enforcer: %w", err)
158 }
159
160 // add this did as owner of this domain
161 err = e.AddKnotOwner(domain, owner)
162 if err != nil {
163 return fmt.Errorf("failed to add knot owner to enforcer: %w", err)
164 }
165
166 err = tx.Commit()
167 if err != nil {
168 return fmt.Errorf("failed to commit changes: %w", err)
169 }
170
171 err = e.E.SavePolicy()
172 if err != nil {
173 return fmt.Errorf("failed to update ACLs: %w", err)
174 }
175 committed = true
176
177 return nil
178}
179func safeDialer(dev bool) *net.Dialer {
180 d := &net.Dialer{
181 Timeout: 5 * time.Second,
182 KeepAlive: 30 * time.Second,
183 }
184 if dev {
185 return d
186 }
187 d.Control = func(network, address string, _ syscall.RawConn) error {
188 host, _, err := net.SplitHostPort(address)
189 if err != nil {
190 return fmt.Errorf("invalid dial address %q: %w", address, err)
191 }
192 ip := net.ParseIP(host)
193 if ip == nil {
194 return fmt.Errorf("dial address %q did not resolve to IP", address)
195 }
196 if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() ||
197 ip.IsLinkLocalMulticast() || ip.IsMulticast() || ip.IsUnspecified() {
198 return fmt.Errorf("refusing to dial %s: reserved or private address", ip)
199 }
200 return nil
201 }
202 return d
203}