This repository has no description
3.6 kB
159 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// MarkKnotVerified marks a knot as verified and sets up ownership/permissions
86func MarkKnotVerified(d *db.DB, e *rbac.Enforcer, domain, owner string) error {
87 tx, err := d.BeginTx(context.Background(), nil)
88 if err != nil {
89 return fmt.Errorf("failed to start tx: %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 as registered
101 err = db.MarkRegistered(
102 tx,
103 orm.FilterEq("did", owner),
104 orm.FilterEq("domain", domain),
105 )
106 if err != nil {
107 return fmt.Errorf("failed to register domain: %w", err)
108 }
109
110 // add basic acls for this domain
111 err = e.AddKnot(domain)
112 if err != nil {
113 return fmt.Errorf("failed to add knot to enforcer: %w", err)
114 }
115
116 // add this did as owner of this domain
117 err = e.AddKnotOwner(domain, owner)
118 if err != nil {
119 return fmt.Errorf("failed to add knot owner to enforcer: %w", err)
120 }
121
122 err = tx.Commit()
123 if err != nil {
124 return fmt.Errorf("failed to commit changes: %w", err)
125 }
126
127 err = e.E.SavePolicy()
128 if err != nil {
129 return fmt.Errorf("failed to update ACLs: %w", err)
130 }
131 committed = true
132
133 return nil
134}
135func safeDialer(dev bool) *net.Dialer {
136 d := &net.Dialer{
137 Timeout: 5 * time.Second,
138 KeepAlive: 30 * time.Second,
139 }
140 if dev {
141 return d
142 }
143 d.Control = func(network, address string, _ syscall.RawConn) error {
144 host, _, err := net.SplitHostPort(address)
145 if err != nil {
146 return fmt.Errorf("invalid dial address %q: %w", address, err)
147 }
148 ip := net.ParseIP(host)
149 if ip == nil {
150 return fmt.Errorf("dial address %q did not resolve to IP", address)
151 }
152 if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() ||
153 ip.IsLinkLocalMulticast() || ip.IsMulticast() || ip.IsUnspecified() {
154 return fmt.Errorf("refusing to dial %s: reserved or private address", ip)
155 }
156 return nil
157 }
158 return d
159}