Skip to content
File

Blob: tun/server/acme_rpc.go

go141 lines
1package server
2 
3import (
4 "bytes"
5 "context"
6 "crypto/ed25519"
7 "errors"
8 "strings"
9 "time"
10 
11 "go.miragespace.co/specter/spec/acme"
12 "go.miragespace.co/specter/spec/chord"
13 "go.miragespace.co/specter/spec/pow"
14 "go.miragespace.co/specter/spec/protocol"
15 "go.miragespace.co/specter/spec/tun"
16 
17 "github.com/twitchtv/twirp"
18 "go.uber.org/zap"
19)
20 
21func (s *Server) checkAcme(ctx context.Context, hostname string, proof *protocol.ProofOfWork, token *protocol.ClientToken, client *protocol.Node) (found bool, err error) {
22 _, err = pow.VerifySolution(proof, pow.Parameters{
23 Difficulty: acme.HashcashDifficulty,
24 Expires: acme.HashcashExpires,
25 GetSubject: func(pubKey ed25519.PublicKey) string {
26 return hostname
27 },
28 })
29 if err != nil {
30 return false, err
31 }
32 
33 if strings.Contains(hostname, s.Acme) || strings.Contains(hostname, s.Apex) {
34 return false, twirp.InvalidArgumentError("hostname", "provided hostname is not valid for custom hostname")
35 }
36 
37 if strings.Count(hostname, ".") < 2 {
38 return false, twirp.InvalidArgumentError("hostname", "custom domain is not supported on bare domain")
39 }
40 
41 bundle, err := tun.FindCustomHostname(ctx, s.Chord, hostname)
42 switch err {
43 case nil:
44 // alreday exist
45 if !bytes.Equal(bundle.GetClientToken().GetToken(), token.GetToken()) ||
46 bundle.GetClientIdentity().GetId() != client.GetId() ||
47 bundle.GetClientIdentity().GetAddress() != client.GetAddress() {
48 return false, twirp.InvalidArgumentError("hostname", "provided hostname is not valid for custom hostname")
49 }
50 return true, nil
51 case tun.ErrHostnameNotFound:
52 // expected
53 return false, nil
54 default:
55 return false, err
56 }
57}
58 
59func (s *Server) AcmeInstruction(ctx context.Context, req *protocol.InstructionRequest) (*protocol.InstructionResponse, error) {
60 token, client, err := extractAuthenticated(ctx)
61 if err != nil {
62 return nil, err
63 }
64 
65 hostname, err := acme.Normalize(req.GetHostname())
66 if err != nil {
67 return nil, twirp.InvalidArgumentError("hostname", err.Error())
68 }
69 
70 if _, err := s.checkAcme(ctx, hostname, req.GetProof(), token, client); err != nil {
71 return nil, err
72 }
73 
74 name, content := acme.GenerateCustomRecord(hostname, s.Acme, token.GetToken())
75 return &protocol.InstructionResponse{
76 Name: name,
77 Content: content,
78 }, nil
79}
80 
81func (s *Server) AcmeValidate(ctx context.Context, req *protocol.ValidateRequest) (*protocol.ValidateResponse, error) {
82 token, client, err := extractAuthenticated(ctx)
83 if err != nil {
84 return nil, err
85 }
86 
87 hostname, err := acme.Normalize(req.GetHostname())
88 if err != nil {
89 return nil, twirp.InvalidArgumentError("hostname", err.Error())
90 }
91 
92 found, err := s.checkAcme(ctx, hostname, req.GetProof(), token, client)
93 if err != nil {
94 return nil, err
95 }
96 
97 var (
98 start time.Time
99 cname string
100 )
101 
102 name, content := acme.GenerateCustomRecord(hostname, s.Acme, token.GetToken())
103 
104 lookupCtx, lookupCancel := context.WithTimeout(ctx, lookupTimeout)
105 defer lookupCancel()
106 
107 if found {
108 goto VALIDATED
109 }
110 
111 start = time.Now()
112 cname, err = s.Resolver.LookupCNAME(lookupCtx, name)
113 if err != nil {
114 return nil, twirp.FailedPrecondition.Error(err.Error())
115 }
116 
117 if cname != content {
118 return nil, twirp.FailedPrecondition.Errorf("unexpected CNAME content: %s", cname)
119 }
120 
121 s.Logger.Info("Custom hostname validated", zap.String("hostname", hostname), zap.Duration("took", time.Since(start)), zap.Object("client", client))
122 
123VALIDATED:
124 if err := tun.SaveCustomHostname(ctx, s.Chord, hostname, &protocol.CustomHostname{
125 ClientIdentity: client,
126 ClientToken: token,
127 }); err != nil {
128 return nil, twirp.InternalErrorWith(err)
129 }
130 
131 prefix := tun.ClientHostnamesPrefix(token)
132 err = s.Chord.PrefixAppend(ctx, []byte(prefix), []byte(hostname))
133 if err != nil && !errors.Is(err, chord.ErrKVPrefixConflict) {
134 return nil, twirp.InternalErrorWith(err)
135 }
136 
137 return &protocol.ValidateResponse{
138 Apex: s.Apex,
139 }, nil
140}