This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / serververify / verify.go
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}