cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2019.5.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

sshgen/sshgen.go

188lines · modecode

1package sshgen
2
3import (
4 "bytes"
5 "crypto/ecdsa"
6 "crypto/elliptic"
7 "crypto/rand"
8 "crypto/x509"
9 "encoding/json"
10 "encoding/pem"
11 "errors"
12 "fmt"
13 "io"
14 "io/ioutil"
15 "net/http"
16 "net/url"
17 "time"
18
19 "github.com/cloudflare/cloudflared/cmd/cloudflared/config"
20 cfpath "github.com/cloudflare/cloudflared/cmd/cloudflared/path"
21 "github.com/coreos/go-oidc/jose"
22 homedir "github.com/mitchellh/go-homedir"
23 gossh "golang.org/x/crypto/ssh"
24)
25
26const (
27 signEndpoint = "/cdn-cgi/access/cert_sign"
28 keyName = "cf_key"
29)
30
31// signPayload represents the request body sent to the sign handler API
32type signPayload struct {
33 PublicKey string `json:"public_key"`
34 JWT string `json:"jwt"`
35 Issuer string `json:"issuer"`
36}
37
38// signResponse represents the response body from the sign handler API
39type signResponse struct {
40 KeyID string `json:"id"`
41 Certificate string `json:"certificate"`
42 ExpiresAt time.Time `json:"expires_at"`
43}
44
45// ErrorResponse struct stores error information after any error-prone function
46type errorResponse struct {
47 Status int `json:"status"`
48 Message string `json:"message"`
49}
50
51var mockRequest func(url, contentType string, body io.Reader) (*http.Response, error) = nil
52
53// GenerateShortLivedCertificate generates and stores a keypair for short lived certs
54func GenerateShortLivedCertificate(appURL *url.URL, token string) error {
55 fullName, err := cfpath.GenerateFilePathFromURL(appURL, keyName)
56 if err != nil {
57 return err
58 }
59
60 cert, err := handleCertificateGeneration(token, fullName)
61 if err != nil {
62 return err
63 }
64
65 name := fullName + "-cert.pub"
66 if err := writeKey(name, []byte(cert)); err != nil {
67 return err
68 }
69
70 return nil
71}
72
73// handleCertificateGeneration takes a JWT and uses it build a signPayload
74// to send to the Sign endpoint with the public key from the keypair it generated
75func handleCertificateGeneration(token, fullName string) (string, error) {
76 if token == "" {
77 return "", errors.New("invalid token")
78 }
79
80 jwt, err := jose.ParseJWT(token)
81 if err != nil {
82 return "", err
83 }
84
85 claims, err := jwt.Claims()
86 if err != nil {
87 return "", err
88 }
89
90 issuer, _, err := claims.StringClaim("iss")
91 if err != nil {
92 return "", err
93 }
94
95 pub, err := generateKeyPair(fullName)
96 if err != nil {
97 return "", err
98 }
99
100 buf, err := json.Marshal(&signPayload{
101 PublicKey: string(pub),
102 JWT: token,
103 Issuer: issuer,
104 })
105 if err != nil {
106 return "", err
107 }
108
109 var res *http.Response
110 if mockRequest != nil {
111 res, err = mockRequest(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
112 } else {
113 res, err = http.Post(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
114 }
115
116 if err != nil {
117 return "", err
118 }
119 defer res.Body.Close()
120
121 decoder := json.NewDecoder(res.Body)
122
123 if res.StatusCode != 200 {
124 var errResponse errorResponse
125 if err := decoder.Decode(&errResponse); err != nil {
126 return "", err
127 }
128 return "", fmt.Errorf("%d: %s", errResponse.Status, errResponse.Message)
129 }
130
131 var signRes signResponse
132 if err := decoder.Decode(&signRes); err != nil {
133 return "", err
134 }
135 return signRes.Certificate, err
136}
137
138// generateKeyPair creates a EC keypair (P256) and stores them in the homedir.
139// returns the generated public key from the successful keypair generation
140func generateKeyPair(fullName string) ([]byte, error) {
141 pubKeyName := fullName + ".pub"
142
143 exist, err := config.FileExists(pubKeyName)
144 if err != nil {
145 return nil, err
146 }
147 if exist {
148 return ioutil.ReadFile(pubKeyName)
149 }
150
151 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
152 if err != nil {
153 return nil, err
154 }
155 parsed, err := x509.MarshalECPrivateKey(key)
156 if err != nil {
157 return nil, err
158 }
159
160 if err := writeKey(fullName, pem.EncodeToMemory(&pem.Block{
161 Type: "EC PRIVATE KEY",
162 Bytes: parsed,
163 })); err != nil {
164 return nil, err
165 }
166
167 pub, err := gossh.NewPublicKey(&key.PublicKey)
168 if err != nil {
169 return nil, err
170 }
171 data := gossh.MarshalAuthorizedKey(pub)
172
173 if err := writeKey(pubKeyName, data); err != nil {
174 return nil, err
175 }
176
177 return data, nil
178}
179
180// writeKey will write a key to disk in DER format (it's a standard pem key)
181func writeKey(filename string, data []byte) error {
182 filepath, err := homedir.Expand(filename)
183 if err != nil {
184 return err
185 }
186
187 return ioutil.WriteFile(filepath, data, 0600)
188}
189