Release 2017.11.1
This commit is contained in:
		
							parent
							
								
									82cb539fbe
								
							
						
					
					
						commit
						ff67bd23f2
					
				| 
						 | 
					@ -4,7 +4,27 @@ import (
 | 
				
			||||||
	"crypto/x509"
 | 
						"crypto/x509"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cloudflareRootCA = `-----BEGIN CERTIFICATE-----
 | 
					// TODO: remove the Origin CA root certs when migrated to Authenticated Origin Pull certs
 | 
				
			||||||
 | 
					const cloudflareRootCA = `
 | 
				
			||||||
 | 
					Issuer: C=US, ST=California, L=San Francisco, O=CloudFlare, Inc., OU=CloudFlare Origin SSL ECC Certificate Authority
 | 
				
			||||||
 | 
					-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIICiDCCAi6gAwIBAgIUXZP3MWb8MKwBE1Qbawsp1sfA/Y4wCgYIKoZIzj0EAwIw
 | 
				
			||||||
 | 
					gY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T
 | 
				
			||||||
 | 
					YW4gRnJhbmNpc2NvMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYDVQQL
 | 
				
			||||||
 | 
					Ey9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhvcml0
 | 
				
			||||||
 | 
					eTAeFw0xNjAyMjIxODI0MDBaFw0yMTAyMjIwMDI0MDBaMIGPMQswCQYDVQQGEwJV
 | 
				
			||||||
 | 
					UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEZ
 | 
				
			||||||
 | 
					MBcGA1UEChMQQ2xvdWRGbGFyZSwgSW5jLjE4MDYGA1UECxMvQ2xvdWRGbGFyZSBP
 | 
				
			||||||
 | 
					cmlnaW4gU1NMIEVDQyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwWTATBgcqhkjOPQIB
 | 
				
			||||||
 | 
					BggqhkjOPQMBBwNCAASR+sGALuaGshnUbcxKry+0LEXZ4NY6JUAtSeA6g87K3jaA
 | 
				
			||||||
 | 
					xpIg9G50PokpfWkhbarLfpcZu0UAoYy2su0EhN7wo2YwZDAOBgNVHQ8BAf8EBAMC
 | 
				
			||||||
 | 
					AQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUhTBdOypw1O3VkmcH/es5
 | 
				
			||||||
 | 
					tBoOOKcwHwYDVR0jBBgwFoAUhTBdOypw1O3VkmcH/es5tBoOOKcwCgYIKoZIzj0E
 | 
				
			||||||
 | 
					AwIDSAAwRQIgEiIEHQr5UKma50D1WRMJBUSgjg24U8n8E2mfw/8UPz0CIQCr5V/e
 | 
				
			||||||
 | 
					mcifak4CQsr+DH4pn5SJD7JxtCG3YGswW8QZsw==
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----
 | 
				
			||||||
 | 
					Issuer: C=US, O=CloudFlare, Inc., OU=CloudFlare Origin SSL Certificate Authority, L=San Francisco, ST=California
 | 
				
			||||||
 | 
					-----BEGIN CERTIFICATE-----
 | 
				
			||||||
MIID/DCCAuagAwIBAgIID+rOSdTGfGcwCwYJKoZIhvcNAQELMIGLMQswCQYDVQQG
 | 
					MIID/DCCAuagAwIBAgIID+rOSdTGfGcwCwYJKoZIhvcNAQELMIGLMQswCQYDVQQG
 | 
				
			||||||
EwJVUzEZMBcGA1UEChMQQ2xvdWRGbGFyZSwgSW5jLjE0MDIGA1UECxMrQ2xvdWRG
 | 
					EwJVUzEZMBcGA1UEChMQQ2xvdWRGbGFyZSwgSW5jLjE0MDIGA1UECxMrQ2xvdWRG
 | 
				
			||||||
bGFyZSBPcmlnaW4gU1NMIENlcnRpZmljYXRlIEF1dGhvcml0eTEWMBQGA1UEBxMN
 | 
					bGFyZSBPcmlnaW4gU1NMIENlcnRpZmljYXRlIEF1dGhvcml0eTEWMBQGA1UEBxMN
 | 
				
			||||||
| 
						 | 
					@ -27,6 +47,42 @@ QGgDl6gRmb8aDwk7Q92BPvek5nMzaWlP82ixavvYI+okoSY8pwdcVKobx6rWzMWz
 | 
				
			||||||
ZEC9M6H3F0dDYE23XcCFIdgNSAmmGyXPBstOe0aAJXwJTxOEPn36VWr0PKIQJy5Y
 | 
					ZEC9M6H3F0dDYE23XcCFIdgNSAmmGyXPBstOe0aAJXwJTxOEPn36VWr0PKIQJy5Y
 | 
				
			||||||
4o1wpMpqCOIwWc8J9REV/REzN6Z1LXImdUgXIXOwrz56gKUJzPejtBQyIGj0mveX
 | 
					4o1wpMpqCOIwWc8J9REV/REzN6Z1LXImdUgXIXOwrz56gKUJzPejtBQyIGj0mveX
 | 
				
			||||||
Fu6q54beR89jDc+oABmOgg==
 | 
					Fu6q54beR89jDc+oABmOgg==
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----
 | 
				
			||||||
 | 
					Issuer: C=US, O=CloudFlare, Inc., OU=Origin Pull, L=San Francisco, ST=California, CN=origin-pull.cloudflare.net
 | 
				
			||||||
 | 
					-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIIGBjCCA/CgAwIBAgIIV5G6lVbCLmEwCwYJKoZIhvcNAQENMIGQMQswCQYDVQQG
 | 
				
			||||||
 | 
					EwJVUzEZMBcGA1UEChMQQ2xvdWRGbGFyZSwgSW5jLjEUMBIGA1UECxMLT3JpZ2lu
 | 
				
			||||||
 | 
					IFB1bGwxFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAgTCkNhbGlmb3Ju
 | 
				
			||||||
 | 
					aWExIzAhBgNVBAMTGm9yaWdpbi1wdWxsLmNsb3VkZmxhcmUubmV0MB4XDTE1MDEx
 | 
				
			||||||
 | 
					MzAyNDc1M1oXDTIwMDExMjAyNTI1M1owgZAxCzAJBgNVBAYTAlVTMRkwFwYDVQQK
 | 
				
			||||||
 | 
					ExBDbG91ZEZsYXJlLCBJbmMuMRQwEgYDVQQLEwtPcmlnaW4gUHVsbDEWMBQGA1UE
 | 
				
			||||||
 | 
					BxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5pYTEjMCEGA1UEAxMa
 | 
				
			||||||
 | 
					b3JpZ2luLXB1bGwuY2xvdWRmbGFyZS5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4IC
 | 
				
			||||||
 | 
					DwAwggIKAoICAQDdsts6I2H5dGyn4adACQRXlfo0KmwsN7B5rxD8C5qgy6spyONr
 | 
				
			||||||
 | 
					WV0ecvdeGQfWa8Gy/yuTuOnsXfy7oyZ1dm93c3Mea7YkM7KNMc5Y6m520E9tHooc
 | 
				
			||||||
 | 
					f1qxeDpGSsnWc7HWibFgD7qZQx+T+yfNqt63vPI0HYBOYao6hWd3JQhu5caAcIS2
 | 
				
			||||||
 | 
					ms5tzSSZVH83ZPe6Lkb5xRgLl3eXEFcfI2DjnlOtLFqpjHuEB3Tr6agfdWyaGEEi
 | 
				
			||||||
 | 
					lRY1IB3k6TfLTaSiX2/SyJ96bp92wvTSjR7USjDV9ypf7AD6u6vwJZ3bwNisNw5L
 | 
				
			||||||
 | 
					ptph0FBnc1R6nDoHmvQRoyytoe0rl/d801i9Nru/fXa+l5K2nf1koR3IX440Z2i9
 | 
				
			||||||
 | 
					+Z4iVA69NmCbT4MVjm7K3zlOtwfI7i1KYVv+ATo4ycgBuZfY9f/2lBhIv7BHuZal
 | 
				
			||||||
 | 
					b9D+/EK8aMUfjDF4icEGm+RQfExv2nOpkR4BfQppF/dLmkYfjgtO1403X0ihkT6T
 | 
				
			||||||
 | 
					PYQdmYS6Jf53/KpqC3aA+R7zg2birtvprinlR14MNvwOsDOzsK4p8WYsgZOR4Qr2
 | 
				
			||||||
 | 
					gAx+z2aVOs/87+TVOR0r14irQsxbg7uP2X4t+EXx13glHxwG+CnzUVycDLMVGvuG
 | 
				
			||||||
 | 
					aUgF9hukZxlOZnrl6VOf1fg0Caf3uvV8smOkVw6DMsGhBZSJVwao0UQNqQIDAQAB
 | 
				
			||||||
 | 
					o2YwZDAOBgNVHQ8BAf8EBAMCAAYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4E
 | 
				
			||||||
 | 
					FgQUQ1lLK2mLgOERM2pXzVc42p59xeswHwYDVR0jBBgwFoAUQ1lLK2mLgOERM2pX
 | 
				
			||||||
 | 
					zVc42p59xeswCwYJKoZIhvcNAQENA4ICAQDKDQM1qPRVP/4Gltz0D6OU6xezFBKr
 | 
				
			||||||
 | 
					LWtDoA1qW2F7pkiYawCP9MrDPDJsHy7dx+xw3bBZxOsK5PA/T7p1dqpEl6i8F692
 | 
				
			||||||
 | 
					g//EuYOifLYw3ySPe3LRNhvPl/1f6Sn862VhPvLa8aQAAwR9e/CZvlY3fj+6G5ik
 | 
				
			||||||
 | 
					3it7fikmKUsVnugNOkjmwI3hZqXfJNc7AtHDFw0mEOV0dSeAPTo95N9cxBbm9PKv
 | 
				
			||||||
 | 
					qAEmTEXp2trQ/RjJ/AomJyfA1BQjsD0j++DI3a9/BbDwWmr1lJciKxiNKaa0BRLB
 | 
				
			||||||
 | 
					dKMrYQD+PkPNCgEuojT+paLKRrMyFUzHSG1doYm46NE9/WARTh3sFUp1B7HZSBqA
 | 
				
			||||||
 | 
					kHleoB/vQ/mDuW9C3/8Jk2uRUdZxR+LoNZItuOjU8oTy6zpN1+GgSj7bHjiy9rfA
 | 
				
			||||||
 | 
					F+ehdrz+IOh80WIiqs763PGoaYUyzxLvVowLWNoxVVoc9G+PqFKqD988XlipHVB6
 | 
				
			||||||
 | 
					Bz+1CD4D/bWrs3cC9+kk/jFmrrAymZlkFX8tDb5aXASSLJjUjcptci9SKqtI2h0J
 | 
				
			||||||
 | 
					wUGkD7+bQAr+7vr8/R+CBmNMe7csE8NeEX6lVMF7Dh0a1YKQa6hUN18bBuYgTMuT
 | 
				
			||||||
 | 
					QzMmZpRpIBB321ZBlcnlxiTJvWxvbCPHKHj20VwwAz7LONF59s84ZsOqfoBv8gKM
 | 
				
			||||||
 | 
					s0s5dsq5zpLeaw==
 | 
				
			||||||
-----END CERTIFICATE-----`
 | 
					-----END CERTIFICATE-----`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetCloudflareRootCA() *x509.CertPool {
 | 
					func GetCloudflareRootCA() *x509.CertPool {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,23 +4,21 @@ import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/pkg/errors"
 | 
						"github.com/pkg/errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log "github.com/Sirupsen/logrus"
 | 
						log "github.com/Sirupsen/logrus"
 | 
				
			||||||
	"github.com/cloudflare/cloudflare-warp/origin"
 | 
					 | 
				
			||||||
	tunnelpogs "github.com/cloudflare/cloudflare-warp/tunnelrpc/pogs"
 | 
					 | 
				
			||||||
	cli "gopkg.in/urfave/cli.v2"
 | 
						cli "gopkg.in/urfave/cli.v2"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type templateData struct {
 | 
					type templateData struct {
 | 
				
			||||||
	ServerName string
 | 
						ServerName string
 | 
				
			||||||
	Request    *http.Request
 | 
						Request    *http.Request
 | 
				
			||||||
	Tags       []tunnelpogs.Tag
 | 
						Body       string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultServerName = "the Cloudflare Warp test server"
 | 
					const defaultServerName = "the Cloudflare Warp test server"
 | 
				
			||||||
| 
						 | 
					@ -42,7 +40,7 @@ const indexTemplate = `
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
  <body class="sans-serif black">
 | 
					  <body class="sans-serif black">
 | 
				
			||||||
    <div class="bt bw2 b--orange bg-white pb6"> 
 | 
					    <div class="bt bw2 b--orange bg-white pb6">
 | 
				
			||||||
      <div class="mw7 center ph4 pt3">
 | 
					      <div class="mw7 center ph4 pt3">
 | 
				
			||||||
        <svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 109 40.5" class="mw4">
 | 
					        <svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 109 40.5" class="mw4">
 | 
				
			||||||
          <path class="st0" d="M98.6 14.2L93 12.9l-1-.4-25.7.2v12.4l32.3.1z"/>
 | 
					          <path class="st0" d="M98.6 14.2L93 12.9l-1-.4-25.7.2v12.4l32.3.1z"/>
 | 
				
			||||||
| 
						 | 
					@ -56,21 +54,30 @@ const indexTemplate = `
 | 
				
			||||||
          running an encrypted, virtual tunnel from your laptop or server to
 | 
					          running an encrypted, virtual tunnel from your laptop or server to
 | 
				
			||||||
          Cloudflare's edge network.
 | 
					          Cloudflare's edge network.
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
        <p class="b f5 mt5 fw6">Ready for the next step?</p> 
 | 
					        <p class="b f5 mt5 fw6">Ready for the next step?</p>
 | 
				
			||||||
        <a 
 | 
					        <a
 | 
				
			||||||
          class="fw6 link white bg-blue ph4 pv2 br1 dib f5 link-hover" 
 | 
					          class="fw6 link white bg-blue ph4 pv2 br1 dib f5 link-hover"
 | 
				
			||||||
          style="border-bottom: 1px solid #1f679e"
 | 
					          style="border-bottom: 1px solid #1f679e"
 | 
				
			||||||
          href="https://warp.cloudflare.com">
 | 
					          href="https://warp.cloudflare.com">
 | 
				
			||||||
          Get started here
 | 
					          Get started here
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
{{if .Tags}}        <section>
 | 
					       <section>
 | 
				
			||||||
          <h4 class="f6 fw4 pt5 mb2">Connection</h4>
 | 
					          <h4 class="f6 fw4 pt5 mb2">Request</h4>
 | 
				
			||||||
          <dl class="bl bw2 b--orange ph3 pt3 pb2 bg-light-gray f7 code overflow-x-auto mw-100">
 | 
					          <dl class="bl bw2 b--orange ph3 pt3 pb2 bg-light-gray f7 code overflow-x-auto mw-100">
 | 
				
			||||||
{{range .Tags}}            <dt class="ttu mb1">{{.Name}}</dt>
 | 
											<dd class="ml0 mb3 f5">Method: {{.Request.Method}}</dd>
 | 
				
			||||||
            <dd class="ml0 mb3 f5">{{.Value}}</dd>
 | 
											<dd class="ml0 mb3 f5">Protocol: {{.Request.Proto}}</dd>
 | 
				
			||||||
{{end}}          </dl>
 | 
											<dd class="ml0 mb3 f5">Request URL: {{.Request.URL}}</dd>
 | 
				
			||||||
 | 
											<dd class="ml0 mb3 f5">Transfer encoding: {{.Request.TransferEncoding}}</dd>
 | 
				
			||||||
 | 
											<dd class="ml0 mb3 f5">Host: {{.Request.Host}}</dd>
 | 
				
			||||||
 | 
											<dd class="ml0 mb3 f5">Remote address: {{.Request.RemoteAddr}}</dd>
 | 
				
			||||||
 | 
											<dd class="ml0 mb3 f5">Request URI: {{.Request.RequestURI}}</dd>
 | 
				
			||||||
 | 
					{{range $key, $value := .Request.Header}}
 | 
				
			||||||
 | 
											<dd class="ml0 mb3 f5">Header: {{$key}}, Value: {{$value}}</dd>
 | 
				
			||||||
 | 
					{{end}}
 | 
				
			||||||
 | 
											<dd class="ml0 mb3 f5">Body: {{.Body}}</dd>
 | 
				
			||||||
 | 
										</dl>
 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
{{end}}      </div>
 | 
					     </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -127,10 +134,17 @@ func (s *HelloWorldServer) ListenAndServe(address string) error {
 | 
				
			||||||
func (s *HelloWorldServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
					func (s *HelloWorldServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	log.WithField("client", r.RemoteAddr).Infof("%s %s %s", r.Method, r.URL, r.Proto)
 | 
						log.WithField("client", r.RemoteAddr).Infof("%s %s %s", r.Method, r.URL, r.Proto)
 | 
				
			||||||
	var buffer bytes.Buffer
 | 
						var buffer bytes.Buffer
 | 
				
			||||||
	err := s.responseTemplate.Execute(&buffer, &templateData{
 | 
						var body string
 | 
				
			||||||
 | 
						rawBody, err := ioutil.ReadAll(r.Body)
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							body = string(rawBody)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							body = ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = s.responseTemplate.Execute(&buffer, &templateData{
 | 
				
			||||||
		ServerName: s.serverName,
 | 
							ServerName: s.serverName,
 | 
				
			||||||
		Request:    r,
 | 
							Request:    r,
 | 
				
			||||||
		Tags:       tagsFromHeaders(r.Header),
 | 
							Body:       body,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		w.WriteHeader(http.StatusInternalServerError)
 | 
							w.WriteHeader(http.StatusInternalServerError)
 | 
				
			||||||
| 
						 | 
					@ -139,17 +153,3 @@ func (s *HelloWorldServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
		buffer.WriteTo(w)
 | 
							buffer.WriteTo(w)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func tagsFromHeaders(header http.Header) []tunnelpogs.Tag {
 | 
					 | 
				
			||||||
	var tags []tunnelpogs.Tag
 | 
					 | 
				
			||||||
	for headerName, headerValues := range header {
 | 
					 | 
				
			||||||
		trimmed := strings.TrimPrefix(headerName, origin.TagHeaderNamePrefix)
 | 
					 | 
				
			||||||
		if trimmed == headerName {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		for _, value := range headerValues {
 | 
					 | 
				
			||||||
			tags = append(tags, tunnelpogs.Tag{Name: trimmed, Value: value})
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return tags
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,8 @@ func runApp(app *cli.App) {
 | 
				
			||||||
	app.Run(os.Args)
 | 
						app.Run(os.Args)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const serviceConfigDir = "/etc/cloudflare-warp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var systemdTemplates = []ServiceTemplate{
 | 
					var systemdTemplates = []ServiceTemplate{
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		Path: "/etc/systemd/system/cloudflare-warp.service",
 | 
							Path: "/etc/systemd/system/cloudflare-warp.service",
 | 
				
			||||||
| 
						 | 
					@ -39,8 +41,7 @@ After=network.target
 | 
				
			||||||
[Service]
 | 
					[Service]
 | 
				
			||||||
TimeoutStartSec=0
 | 
					TimeoutStartSec=0
 | 
				
			||||||
Type=notify
 | 
					Type=notify
 | 
				
			||||||
ExecStart={{ .Path }} --config /etc/cloudflare-warp.yml --autoupdate 0s
 | 
					ExecStart={{ .Path }} --config /etc/cloudflare-warp/config.yml --origincert /etc/cloudflare-warp/cert.pem --autoupdate 0s
 | 
				
			||||||
User=nobody
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Install]
 | 
					[Install]
 | 
				
			||||||
WantedBy=multi-user.target
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
| 
						 | 
					@ -86,7 +87,7 @@ var sysvTemplate = ServiceTemplate{
 | 
				
			||||||
# Short-Description: Cloudflare Warp
 | 
					# Short-Description: Cloudflare Warp
 | 
				
			||||||
# Description:       Cloudflare Warp agent
 | 
					# Description:       Cloudflare Warp agent
 | 
				
			||||||
### END INIT INFO
 | 
					### END INIT INFO
 | 
				
			||||||
cmd="{{.Path}} --config /etc/cloudflare-warp.yml --pidfile /var/run/$name.pid"
 | 
					cmd="{{.Path}} --config /etc/cloudflare-warp/config.yml --origincert /etc/cloudflare-warp/cert.pem --pidfile /var/run/$name.pid"
 | 
				
			||||||
name=$(basename $(readlink -f $0))
 | 
					name=$(basename $(readlink -f $0))
 | 
				
			||||||
pid_file="/var/run/$name.pid"
 | 
					pid_file="/var/run/$name.pid"
 | 
				
			||||||
stdout_log="/var/log/$name.log"
 | 
					stdout_log="/var/log/$name.log"
 | 
				
			||||||
| 
						 | 
					@ -177,6 +178,12 @@ func installLinuxService(c *cli.Context) error {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	templateArgs := ServiceTemplateArgs{Path: etPath}
 | 
						templateArgs := ServiceTemplateArgs{Path: etPath}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = copyCredentials(serviceConfigDir); err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, "Failed to copy user configuration: %v\n", err)
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, "Before running the service, ensure that %s contains two files, %s and %s",
 | 
				
			||||||
 | 
								serviceConfigDir, credentialFile, configFile)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
	case isSystemd():
 | 
						case isSystemd():
 | 
				
			||||||
		return installSystemd(&templateArgs)
 | 
							return installSystemd(&templateArgs)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,31 +1,195 @@
 | 
				
			||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"encoding/base32"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
	"syscall"
 | 
						"syscall"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log "github.com/Sirupsen/logrus"
 | 
				
			||||||
	homedir "github.com/mitchellh/go-homedir"
 | 
						homedir "github.com/mitchellh/go-homedir"
 | 
				
			||||||
	cli "gopkg.in/urfave/cli.v2"
 | 
						cli "gopkg.in/urfave/cli.v2"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const baseLoginURL = "https://www.cloudflare.com/a/warp"
 | 
				
			||||||
 | 
					const baseCertStoreURL = "https://login.cloudflarewarp.com"
 | 
				
			||||||
 | 
					const clientTimeout = time.Minute * 20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func login(c *cli.Context) error {
 | 
					func login(c *cli.Context) error {
 | 
				
			||||||
	path, err := homedir.Expand(defaultConfigPath)
 | 
						configPath, err := homedir.Expand(defaultConfigDir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						ok, err := fileExists(configPath)
 | 
				
			||||||
 | 
						if !ok && err == nil {
 | 
				
			||||||
 | 
							// create config directory if doesn't already exist
 | 
				
			||||||
 | 
							err = os.Mkdir(configPath, 0700)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						path := filepath.Join(configPath, credentialFile)
 | 
				
			||||||
	fileInfo, err := os.Stat(path)
 | 
						fileInfo, err := os.Stat(path)
 | 
				
			||||||
	if err == nil && fileInfo.Size() > 0 {
 | 
						if err == nil && fileInfo.Size() > 0 {
 | 
				
			||||||
		fmt.Fprintf(os.Stderr, `You have an existing config file at %s which login would overwrite.
 | 
							fmt.Fprintf(os.Stderr, `You have an existing certificate at %s which login would overwrite.
 | 
				
			||||||
If this is intentional, please move or delete that file then run this command again.
 | 
					If this is intentional, please move or delete that file then run this command again.
 | 
				
			||||||
`, defaultConfigPath)
 | 
					`, path)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err != nil && err.(*os.PathError).Err != syscall.ENOENT {
 | 
						if err != nil && err.(*os.PathError).Err != syscall.ENOENT {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fmt.Fprintln(os.Stderr, "Please visit https://www.cloudflare.com/a/warp to obtain a certificate.")
 | 
						// for local debugging
 | 
				
			||||||
 | 
						baseURL := baseCertStoreURL
 | 
				
			||||||
 | 
						if c.IsSet("url") {
 | 
				
			||||||
 | 
							baseURL = c.String("url")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Generate a random post URL
 | 
				
			||||||
 | 
						certURL := baseURL + generateRandomPath()
 | 
				
			||||||
 | 
						loginURL, err := url.Parse(baseLoginURL)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// shouldn't happen, URL is hardcoded
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						loginURL.RawQuery = "callback=" + url.QueryEscape(certURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = open(loginURL.String())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, `Please open the following URL and log in with your Cloudflare account:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Leave cloudflare-warp running to install the certificate automatically.
 | 
				
			||||||
 | 
					`, loginURL.String())
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, `A browser window should have opened at the following URL:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If the browser failed to open, open it yourself and visit the URL above.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`, loginURL.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if download(certURL, path) {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, `You have successfully logged in.
 | 
				
			||||||
 | 
					If you wish to copy your credentials to a server, they have been saved to:
 | 
				
			||||||
 | 
					%s
 | 
				
			||||||
 | 
					`, path)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, `Failed to write the certificate due to the following error:
 | 
				
			||||||
 | 
					%v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Your browser will download the certificate instead. You will have to manually
 | 
				
			||||||
 | 
					copy it to the following path:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`, err, path)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// generateRandomPath generates a random URL to associate with the certificate.
 | 
				
			||||||
 | 
					func generateRandomPath() string {
 | 
				
			||||||
 | 
						randomBytes := make([]byte, 40)
 | 
				
			||||||
 | 
						_, err := rand.Read(randomBytes)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "/" + base32.StdEncoding.EncodeToString(randomBytes)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// open opens the specified URL in the default browser of the user.
 | 
				
			||||||
 | 
					func open(url string) error {
 | 
				
			||||||
 | 
						var cmd string
 | 
				
			||||||
 | 
						var args []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch runtime.GOOS {
 | 
				
			||||||
 | 
						case "windows":
 | 
				
			||||||
 | 
							cmd = "cmd"
 | 
				
			||||||
 | 
							args = []string{"/c", "start"}
 | 
				
			||||||
 | 
						case "darwin":
 | 
				
			||||||
 | 
							cmd = "open"
 | 
				
			||||||
 | 
						default: // "linux", "freebsd", "openbsd", "netbsd"
 | 
				
			||||||
 | 
							cmd = "xdg-open"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						args = append(args, url)
 | 
				
			||||||
 | 
						return exec.Command(cmd, args...).Start()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func download(certURL, filePath string) bool {
 | 
				
			||||||
 | 
						client := &http.Client{Timeout: clientTimeout}
 | 
				
			||||||
 | 
						// attempt a (long-running) certificate get
 | 
				
			||||||
 | 
						for i := 0; i < 20; i++ {
 | 
				
			||||||
 | 
							ok, err := tryDownload(client, certURL, filePath)
 | 
				
			||||||
 | 
							if ok {
 | 
				
			||||||
 | 
								putSuccess(client, certURL)
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithError(err).Error("Error fetching certificate")
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func tryDownload(client *http.Client, certURL, filePath string) (ok bool, err error) {
 | 
				
			||||||
 | 
						resp, err := client.Get(certURL)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
						if resp.StatusCode == 404 {
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if resp.StatusCode != 200 {
 | 
				
			||||||
 | 
							return false, fmt.Errorf("Unexpected HTTP error code %d", resp.StatusCode)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if resp.Header.Get("Content-Type") != "application/x-pem-file" {
 | 
				
			||||||
 | 
							return false, fmt.Errorf("Unexpected content type %s", resp.Header.Get("Content-Type"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// write response
 | 
				
			||||||
 | 
						file, err := os.Create(filePath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer file.Close()
 | 
				
			||||||
 | 
						written, err := io.Copy(file, resp.Body)
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case err != nil:
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						case resp.ContentLength != written && resp.ContentLength != -1:
 | 
				
			||||||
 | 
							return false, fmt.Errorf("Short read (%d bytes) from server while writing certificate", written)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func putSuccess(client *http.Client, certURL string) {
 | 
				
			||||||
 | 
						// indicate success to the relay server
 | 
				
			||||||
 | 
						req, err := http.NewRequest("PUT", certURL+"/ok", nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithError(err).Error("HTTP request error")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						resp, err := client.Do(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithError(err).Error("HTTP error")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						resp.Body.Close()
 | 
				
			||||||
 | 
						if resp.StatusCode != 200 {
 | 
				
			||||||
 | 
							log.Errorf("Unexpected HTTP error code %d", resp.StatusCode)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,10 +4,12 @@ import (
 | 
				
			||||||
	"crypto/tls"
 | 
						"crypto/tls"
 | 
				
			||||||
	"encoding/hex"
 | 
						"encoding/hex"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"os/signal"
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"syscall"
 | 
						"syscall"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
| 
						 | 
					@ -31,7 +33,9 @@ import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b:3e8827f6f9f740738eb11138f7bebb68@sentry.io/189878"
 | 
					const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b:3e8827f6f9f740738eb11138f7bebb68@sentry.io/189878"
 | 
				
			||||||
const defaultConfigPath = "~/.cloudflare-warp.yml"
 | 
					const defaultConfigDir = "~/.cloudflare-warp"
 | 
				
			||||||
 | 
					const credentialFile = "cert.pem"
 | 
				
			||||||
 | 
					const configFile = "config.yml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var listeners = gracenet.Net{}
 | 
					var listeners = gracenet.Net{}
 | 
				
			||||||
var Version = "DEV"
 | 
					var Version = "DEV"
 | 
				
			||||||
| 
						 | 
					@ -71,10 +75,15 @@ WARNING:
 | 
				
			||||||
			Usage: "Specifies a config file in YAML format.",
 | 
								Usage: "Specifies a config file in YAML format.",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		altsrc.NewDurationFlag(&cli.DurationFlag{
 | 
							altsrc.NewDurationFlag(&cli.DurationFlag{
 | 
				
			||||||
			Name:  "autoupdate",
 | 
								Name:  "autoupdate-freq",
 | 
				
			||||||
			Usage: "Periodically check for updates, restarting the server with the new version.",
 | 
								Usage: "Autoupdate frequency. Default is 24h.",
 | 
				
			||||||
			Value: time.Hour * 24,
 | 
								Value: time.Hour * 24,
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
 | 
							altsrc.NewBoolFlag(&cli.BoolFlag{
 | 
				
			||||||
 | 
								Name:  "no-autoupdate",
 | 
				
			||||||
 | 
								Usage: "Disable periodic check for updates, restarting the server with the new version.",
 | 
				
			||||||
 | 
								Value: false,
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
		altsrc.NewStringFlag(&cli.StringFlag{
 | 
							altsrc.NewStringFlag(&cli.StringFlag{
 | 
				
			||||||
			Name:    "edge",
 | 
								Name:    "edge",
 | 
				
			||||||
			Value:   "cftunnel.com:7844",
 | 
								Value:   "cftunnel.com:7844",
 | 
				
			||||||
| 
						 | 
					@ -88,6 +97,12 @@ WARNING:
 | 
				
			||||||
			EnvVars: []string{"TUNNEL_CACERT"},
 | 
								EnvVars: []string{"TUNNEL_CACERT"},
 | 
				
			||||||
			Hidden:  true,
 | 
								Hidden:  true,
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
 | 
							altsrc.NewStringFlag(&cli.StringFlag{
 | 
				
			||||||
 | 
								Name:    "origincert",
 | 
				
			||||||
 | 
								Usage:   "Path to the certificate generated for your origin when you run cloudflare-warp login.",
 | 
				
			||||||
 | 
								EnvVars: []string{"ORIGIN_CERT"},
 | 
				
			||||||
 | 
								Value:   filepath.Join(defaultConfigDir, credentialFile),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
		altsrc.NewStringFlag(&cli.StringFlag{
 | 
							altsrc.NewStringFlag(&cli.StringFlag{
 | 
				
			||||||
			Name:    "url",
 | 
								Name:    "url",
 | 
				
			||||||
			Value:   "http://localhost:8080",
 | 
								Value:   "http://localhost:8080",
 | 
				
			||||||
| 
						 | 
					@ -112,18 +127,21 @@ WARNING:
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		altsrc.NewStringFlag(&cli.StringFlag{
 | 
							altsrc.NewStringFlag(&cli.StringFlag{
 | 
				
			||||||
			Name:    "api-key",
 | 
								Name:    "api-key",
 | 
				
			||||||
			Usage:   "A Cloudflare API key. Required(can be in the config file) unless you are only running the hello command or login command.",
 | 
								Usage:   "This parameter has been deprecated since version 2017.10.1.",
 | 
				
			||||||
			EnvVars: []string{"TUNNEL_API_KEY"},
 | 
								EnvVars: []string{"TUNNEL_API_KEY"},
 | 
				
			||||||
 | 
								Hidden:  true,
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		altsrc.NewStringFlag(&cli.StringFlag{
 | 
							altsrc.NewStringFlag(&cli.StringFlag{
 | 
				
			||||||
			Name:    "api-email",
 | 
								Name:    "api-email",
 | 
				
			||||||
			Usage:   "The Cloudflare user's email address associated with the API key. Required(can be in the config file) unless you are only running the hello command or login command.",
 | 
								Usage:   "This parameter has been deprecated since version 2017.10.1.",
 | 
				
			||||||
			EnvVars: []string{"TUNNEL_API_EMAIL"},
 | 
								EnvVars: []string{"TUNNEL_API_EMAIL"},
 | 
				
			||||||
 | 
								Hidden:  true,
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		altsrc.NewStringFlag(&cli.StringFlag{
 | 
							altsrc.NewStringFlag(&cli.StringFlag{
 | 
				
			||||||
			Name:    "api-ca-key",
 | 
								Name:    "api-ca-key",
 | 
				
			||||||
			Usage:   "The Origin CA service key associated with the user. Required(can be in the config file) unless you are only running the hello command or login command.",
 | 
								Usage:   "This parameter has been deprecated since version 2017.10.1.",
 | 
				
			||||||
			EnvVars: []string{"TUNNEL_API_CA_KEY"},
 | 
								EnvVars: []string{"TUNNEL_API_CA_KEY"},
 | 
				
			||||||
 | 
								Hidden:  true,
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		altsrc.NewStringFlag(&cli.StringFlag{
 | 
							altsrc.NewStringFlag(&cli.StringFlag{
 | 
				
			||||||
			Name:    "metrics",
 | 
								Name:    "metrics",
 | 
				
			||||||
| 
						 | 
					@ -160,12 +178,6 @@ WARNING:
 | 
				
			||||||
			Usage:   "Maximum number of retries for connection/protocol errors.",
 | 
								Usage:   "Maximum number of retries for connection/protocol errors.",
 | 
				
			||||||
			EnvVars: []string{"TUNNEL_RETRIES"},
 | 
								EnvVars: []string{"TUNNEL_RETRIES"},
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		altsrc.NewBoolFlag(&cli.BoolFlag{
 | 
					 | 
				
			||||||
			Name:    "debug",
 | 
					 | 
				
			||||||
			Value:   false,
 | 
					 | 
				
			||||||
			Usage:   "Enable HTTP requests to the autogenerated cftunnel.com domain.",
 | 
					 | 
				
			||||||
			EnvVars: []string{"TUNNEL_DEBUG"},
 | 
					 | 
				
			||||||
		}),
 | 
					 | 
				
			||||||
		altsrc.NewBoolFlag(&cli.BoolFlag{
 | 
							altsrc.NewBoolFlag(&cli.BoolFlag{
 | 
				
			||||||
			Name:  "hello-world",
 | 
								Name:  "hello-world",
 | 
				
			||||||
			Usage: "Run Hello World Server",
 | 
								Usage: "Run Hello World Server",
 | 
				
			||||||
| 
						 | 
					@ -207,6 +219,12 @@ WARNING:
 | 
				
			||||||
			Action:    login,
 | 
								Action:    login,
 | 
				
			||||||
			Usage:     "Generate a configuration file with your login details",
 | 
								Usage:     "Generate a configuration file with your login details",
 | 
				
			||||||
			ArgsUsage: " ",
 | 
								ArgsUsage: " ",
 | 
				
			||||||
 | 
								Flags: []cli.Flag{
 | 
				
			||||||
 | 
									&cli.StringFlag{
 | 
				
			||||||
 | 
										Name:   "url",
 | 
				
			||||||
 | 
										Hidden: true,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		&cli.Command{
 | 
							&cli.Command{
 | 
				
			||||||
			Name:   "hello",
 | 
								Name:   "hello",
 | 
				
			||||||
| 
						 | 
					@ -239,6 +257,7 @@ func startServer(c *cli.Context) {
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.WithError(err).Fatal("Unknown logging level specified")
 | 
							log.WithError(err).Fatal("Unknown logging level specified")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.SetLevel(logLevel)
 | 
						log.SetLevel(logLevel)
 | 
				
			||||||
	hostname, err := validation.ValidateHostname(c.String("hostname"))
 | 
						hostname, err := validation.ValidateHostname(c.String("hostname"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -260,7 +279,6 @@ func startServer(c *cli.Context) {
 | 
				
			||||||
	if c.IsSet("hello-world") {
 | 
						if c.IsSet("hello-world") {
 | 
				
			||||||
		wg.Add(1)
 | 
							wg.Add(1)
 | 
				
			||||||
		listener, err := findAvailablePort()
 | 
							listener, err := findAvailablePort()
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			listener.Close()
 | 
								listener.Close()
 | 
				
			||||||
			log.WithError(err).Fatal("Cannot start Hello World Server")
 | 
								log.WithError(err).Fatal("Cannot start Hello World Server")
 | 
				
			||||||
| 
						 | 
					@ -268,32 +286,50 @@ func startServer(c *cli.Context) {
 | 
				
			||||||
		go func() {
 | 
							go func() {
 | 
				
			||||||
			startHelloWorldServer(listener, shutdownC)
 | 
								startHelloWorldServer(listener, shutdownC)
 | 
				
			||||||
			wg.Done()
 | 
								wg.Done()
 | 
				
			||||||
 | 
								listener.Close()
 | 
				
			||||||
		}()
 | 
							}()
 | 
				
			||||||
		c.Set("url", "http://"+listener.Addr().String())
 | 
							c.Set("url", "http://"+listener.Addr().String())
 | 
				
			||||||
		log.Infof("Starting Hello World Server at %s", c.String("url"))
 | 
							log.Infof("Starting Hello World Server at %s", c.String("url"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	url, err := validateUrl(c)
 | 
						url, err := validateUrl(c)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.WithError(err).Fatal("Error validating url")
 | 
							log.WithError(err).Fatal("Error validating url")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// User must have api-key, api-email and api-ca-key
 | 
					 | 
				
			||||||
	if !c.IsSet("api-key") {
 | 
					 | 
				
			||||||
		log.Fatal("You need to give us your api-key either via the --api-key option or put it in the configuration file. You will also need to give us your api-email and api-ca-key.")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if !c.IsSet("api-email") {
 | 
					 | 
				
			||||||
		log.Fatal("You need to give us your api-email either via the --api-email option or put it in the configuration file. You will also need to give us your api-ca-key.")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if !c.IsSet("api-ca-key") {
 | 
					 | 
				
			||||||
		log.Fatal("You need to give us your api-ca-key either via the --api-ca-key option or put it in the configuration file.")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	log.Infof("Proxying tunnel requests to %s", url)
 | 
						log.Infof("Proxying tunnel requests to %s", url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fail if the user provided an old authentication method
 | 
				
			||||||
 | 
						if c.IsSet("api-key") || c.IsSet("api-email") || c.IsSet("api-ca-key") {
 | 
				
			||||||
 | 
							log.Fatal("You don't need to give us your api-key anymore. Please use the new log in method. Just run cloudflare-warp login")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check that the user has acquired a certificate using the log in command
 | 
				
			||||||
 | 
						originCertPath, err := homedir.Expand(c.String("origincert"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithError(err).Fatalf("Cannot resolve path %s", c.String("origincert"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ok, err := fileExists(originCertPath)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							log.Fatalf(`Cannot find a valid certificate for your origin at the path:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    %s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If the path above is wrong, specify the path with the -origincert option.
 | 
				
			||||||
 | 
					If you don't have a certificate signed by Cloudflare, run the command:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    %s login
 | 
				
			||||||
 | 
					`, originCertPath, os.Args[0])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Easier to send the certificate as []byte via RPC than decoding it at this point
 | 
				
			||||||
 | 
						originCert, err := ioutil.ReadFile(originCertPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithError(err).Fatalf("Cannot read %s to load origin certificate", originCertPath)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	tunnelConfig := &origin.TunnelConfig{
 | 
						tunnelConfig := &origin.TunnelConfig{
 | 
				
			||||||
		EdgeAddr:          c.String("edge"),
 | 
							EdgeAddr:          c.String("edge"),
 | 
				
			||||||
		OriginUrl:         url,
 | 
							OriginUrl:         url,
 | 
				
			||||||
		Hostname:          hostname,
 | 
							Hostname:          hostname,
 | 
				
			||||||
		APIKey:            c.String("api-key"),
 | 
							OriginCert:        originCert,
 | 
				
			||||||
		APIEmail:          c.String("api-email"),
 | 
					 | 
				
			||||||
		APICAKey:          c.String("api-ca-key"),
 | 
					 | 
				
			||||||
		TlsConfig:         &tls.Config{},
 | 
							TlsConfig:         &tls.Config{},
 | 
				
			||||||
		Retries:           c.Uint("retries"),
 | 
							Retries:           c.Uint("retries"),
 | 
				
			||||||
		HeartbeatInterval: c.Duration("heartbeat-interval"),
 | 
							HeartbeatInterval: c.Duration("heartbeat-interval"),
 | 
				
			||||||
| 
						 | 
					@ -302,7 +338,6 @@ func startServer(c *cli.Context) {
 | 
				
			||||||
		ReportedVersion:   Version,
 | 
							ReportedVersion:   Version,
 | 
				
			||||||
		LBPool:            c.String("lb-pool"),
 | 
							LBPool:            c.String("lb-pool"),
 | 
				
			||||||
		Tags:              tags,
 | 
							Tags:              tags,
 | 
				
			||||||
		AccessInternalIP:  c.Bool("debug"),
 | 
					 | 
				
			||||||
		ConnectedSignal:   h2mux.NewSignal(),
 | 
							ConnectedSignal:   h2mux.NewSignal(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -329,7 +364,10 @@ func startServer(c *cli.Context) {
 | 
				
			||||||
		wg.Done()
 | 
							wg.Done()
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	go autoupdate(c.Duration("autoupdate"), shutdownC)
 | 
						if !c.Bool("no-autoupdate") {
 | 
				
			||||||
 | 
							log.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq"))
 | 
				
			||||||
 | 
							go autoupdate(c.Duration("autoupdate-period"), shutdownC)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = WaitForSignal(errC, shutdownC)
 | 
						err = WaitForSignal(errC, shutdownC)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -413,21 +451,14 @@ func findInputSourceContext(context *cli.Context) (altsrc.InputSourceContext, er
 | 
				
			||||||
	if context.IsSet("config") {
 | 
						if context.IsSet("config") {
 | 
				
			||||||
		return altsrc.NewYamlSourceFromFile(context.String("config"))
 | 
							return altsrc.NewYamlSourceFromFile(context.String("config"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, tryPath := range []string{
 | 
						dirPath, err := homedir.Expand(defaultConfigDir)
 | 
				
			||||||
		defaultConfigPath,
 | 
						if err != nil {
 | 
				
			||||||
		"~/.cloudflare-warp.yaml",
 | 
							return nil, nil
 | 
				
			||||||
		"~/cloudflare-warp.yaml",
 | 
						}
 | 
				
			||||||
		"~/cloudflare-warp.yml",
 | 
						for _, path := range []string{
 | 
				
			||||||
		"~/.et.yaml",
 | 
							filepath.Join(dirPath, "/config.yml"),
 | 
				
			||||||
		"~/et.yml",
 | 
							filepath.Join(dirPath, "/config.yaml"),
 | 
				
			||||||
		"~/et.yaml",
 | 
					 | 
				
			||||||
		"~/.cftunnel.yaml", // for existing users
 | 
					 | 
				
			||||||
		"~/cftunnel.yaml",
 | 
					 | 
				
			||||||
	} {
 | 
						} {
 | 
				
			||||||
		path, err := homedir.Expand(tryPath)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		ok, err := fileExists(path)
 | 
							ok, err := fileExists(path)
 | 
				
			||||||
		if ok {
 | 
							if ok {
 | 
				
			||||||
			return altsrc.NewYamlSourceFromFile(path)
 | 
								return altsrc.NewYamlSourceFromFile(path)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,14 @@
 | 
				
			||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"os/exec"
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
	"text/template"
 | 
						"text/template"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	homedir "github.com/mitchellh/go-homedir"
 | 
						homedir "github.com/mitchellh/go-homedir"
 | 
				
			||||||
| 
						 | 
					@ -78,7 +81,7 @@ func runCommand(command string, args ...string) error {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	commandErr, _ := ioutil.ReadAll(stderr)
 | 
						commandErr, _ := ioutil.ReadAll(stderr)
 | 
				
			||||||
	if len(commandErr) > 0 {
 | 
						if len(commandErr) > 0 {
 | 
				
			||||||
		return fmt.Errorf("%s error: %s", command, commandErr)
 | 
							fmt.Fprintf(os.Stderr, "%s: %s", command, commandErr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err = cmd.Wait()
 | 
						err = cmd.Wait()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -86,3 +89,100 @@ func runCommand(command string, args ...string) error {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ensureConfigDirExists(configDir string) error {
 | 
				
			||||||
 | 
						ok, err := fileExists(configDir)
 | 
				
			||||||
 | 
						if !ok && err == nil {
 | 
				
			||||||
 | 
							err = os.Mkdir(configDir, 0700)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// openFile opens the file at path. If create is set and the file exists, returns nil, true, nil
 | 
				
			||||||
 | 
					func openFile(path string, create bool) (file *os.File, exists bool, err error) {
 | 
				
			||||||
 | 
						expandedPath, err := homedir.Expand(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if create {
 | 
				
			||||||
 | 
							fileInfo, err := os.Stat(expandedPath)
 | 
				
			||||||
 | 
							if err == nil && fileInfo.Size() > 0 {
 | 
				
			||||||
 | 
								return nil, true, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							file, err = os.OpenFile(expandedPath, os.O_RDWR|os.O_CREATE, 0600)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							file, err = os.Open(expandedPath)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return file, false, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func copyCertificate(configDir string) error {
 | 
				
			||||||
 | 
						// Copy certificate
 | 
				
			||||||
 | 
						destCredentialPath := filepath.Join(configDir, credentialFile)
 | 
				
			||||||
 | 
						destFile, exists, err := openFile(destCredentialPath, true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						} else if exists {
 | 
				
			||||||
 | 
							// credentials already exist, do nothing
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer destFile.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						srcCredentialPath := filepath.Join(defaultConfigDir, credentialFile)
 | 
				
			||||||
 | 
						srcFile, _, err := openFile(srcCredentialPath, false)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer srcFile.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = io.Copy(destFile, srcFile)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to copy %s to %s: %v", srcCredentialPath, destCredentialPath, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func copyCredentials(configDir string) error {
 | 
				
			||||||
 | 
						if err := ensureConfigDirExists(configDir); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := copyCertificate(configDir); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Copy or create config
 | 
				
			||||||
 | 
						destConfigPath := filepath.Join(configDir, configFile)
 | 
				
			||||||
 | 
						destFile, exists, err := openFile(destConfigPath, true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						} else if exists {
 | 
				
			||||||
 | 
							// config already exists, do nothing
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer destFile.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						srcConfigPath := filepath.Join(defaultConfigDir, configFile)
 | 
				
			||||||
 | 
						srcFile, _, err := openFile(srcConfigPath, false)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Println("Your service needs a config file that at least specifies the hostname option.")
 | 
				
			||||||
 | 
							fmt.Println("Type in a hostname now, or leave it blank and create the config file later.")
 | 
				
			||||||
 | 
							fmt.Print("Hostname: ")
 | 
				
			||||||
 | 
							reader := bufio.NewReader(os.Stdin)
 | 
				
			||||||
 | 
							input, _ := reader.ReadString('\n')
 | 
				
			||||||
 | 
							if input == "" {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fmt.Fprintf(destFile, "hostname: %s\n", input)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							defer srcFile.Close()
 | 
				
			||||||
 | 
							_, err = io.Copy(destFile, srcFile)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("unable to copy %s to %s: %v", srcConfigPath, destConfigPath, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fmt.Printf("Copied %s to %s", srcConfigPath, destConfigPath)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,9 +34,7 @@ type TunnelConfig struct {
 | 
				
			||||||
	EdgeAddr          string
 | 
						EdgeAddr          string
 | 
				
			||||||
	OriginUrl         string
 | 
						OriginUrl         string
 | 
				
			||||||
	Hostname          string
 | 
						Hostname          string
 | 
				
			||||||
	APIKey            string
 | 
						OriginCert        []byte
 | 
				
			||||||
	APIEmail          string
 | 
					 | 
				
			||||||
	APICAKey          string
 | 
					 | 
				
			||||||
	TlsConfig         *tls.Config
 | 
						TlsConfig         *tls.Config
 | 
				
			||||||
	Retries           uint
 | 
						Retries           uint
 | 
				
			||||||
	HeartbeatInterval time.Duration
 | 
						HeartbeatInterval time.Duration
 | 
				
			||||||
| 
						 | 
					@ -45,7 +43,6 @@ type TunnelConfig struct {
 | 
				
			||||||
	ReportedVersion   string
 | 
						ReportedVersion   string
 | 
				
			||||||
	LBPool            string
 | 
						LBPool            string
 | 
				
			||||||
	Tags              []tunnelpogs.Tag
 | 
						Tags              []tunnelpogs.Tag
 | 
				
			||||||
	AccessInternalIP  bool
 | 
					 | 
				
			||||||
	ConnectedSignal   h2mux.Signal
 | 
						ConnectedSignal   h2mux.Signal
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,7 +75,6 @@ func (c *TunnelConfig) RegistrationOptions() *tunnelpogs.RegistrationOptions {
 | 
				
			||||||
		ExistingTunnelPolicy: policy,
 | 
							ExistingTunnelPolicy: policy,
 | 
				
			||||||
		PoolID:               c.LBPool,
 | 
							PoolID:               c.LBPool,
 | 
				
			||||||
		Tags:                 c.Tags,
 | 
							Tags:                 c.Tags,
 | 
				
			||||||
		ExposeInternalHostname: c.AccessInternalIP,
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,15 +142,16 @@ func ServeTunnel(
 | 
				
			||||||
		return err, true
 | 
							return err, true
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if registerErr != nil {
 | 
						if registerErr != nil {
 | 
				
			||||||
		raven.CaptureError(registerErr, nil)
 | 
					 | 
				
			||||||
		// Don't retry on errors like entitlement failure or version too old
 | 
							// Don't retry on errors like entitlement failure or version too old
 | 
				
			||||||
		if e, ok := registerErr.(printableRegisterTunnelError); ok {
 | 
							if e, ok := registerErr.(printableRegisterTunnelError); ok {
 | 
				
			||||||
			log.WithError(e).Error("Cannot register")
 | 
								log.Error(e)
 | 
				
			||||||
			if e.permanent {
 | 
								if e.permanent {
 | 
				
			||||||
				return nil, false
 | 
									return nil, false
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return e.cause, true
 | 
								return e.cause, true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							// Only log errors to Sentry that may have been caused by the client side, to reduce dupes
 | 
				
			||||||
 | 
							raven.CaptureError(registerErr, nil)
 | 
				
			||||||
		log.Error("Cannot register")
 | 
							log.Error("Cannot register")
 | 
				
			||||||
		return err, true
 | 
							return err, true
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -202,7 +199,7 @@ func RegisterTunnel(ctx context.Context, muxer *h2mux.Muxer, config *TunnelConfi
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	registration, err := ts.RegisterTunnel(
 | 
						registration, err := ts.RegisterTunnel(
 | 
				
			||||||
		ctx,
 | 
							ctx,
 | 
				
			||||||
		&tunnelpogs.Authentication{Key: config.APIKey, Email: config.APIEmail, OriginCAKey: config.APICAKey},
 | 
							config.OriginCert,
 | 
				
			||||||
		config.Hostname,
 | 
							config.Hostname,
 | 
				
			||||||
		config.RegistrationOptions(),
 | 
							config.RegistrationOptions(),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
| 
						 | 
					@ -220,9 +217,9 @@ func RegisterTunnel(ctx context.Context, muxer *h2mux.Muxer, config *TunnelConfi
 | 
				
			||||||
			permanent: registration.PermanentFailure,
 | 
								permanent: registration.PermanentFailure,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, url := range registration.Urls {
 | 
					
 | 
				
			||||||
		log.Infof("Registered at %s", url)
 | 
						log.Infof("Registered at %s", registration.Url)
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	for _, logLine := range registration.LogLines {
 | 
						for _, logLine := range registration.LogLines {
 | 
				
			||||||
		log.Infof(logLine)
 | 
							log.Infof(logLine)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ func UnmarshalAuthentication(s tunnelrpc.Authentication) (*Authentication, error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TunnelRegistration struct {
 | 
					type TunnelRegistration struct {
 | 
				
			||||||
	Err              string
 | 
						Err              string
 | 
				
			||||||
	Urls             []string
 | 
						Url              string
 | 
				
			||||||
	LogLines         []string
 | 
						LogLines         []string
 | 
				
			||||||
	PermanentFailure bool
 | 
						PermanentFailure bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,6 @@ type RegistrationOptions struct {
 | 
				
			||||||
	OS                     string `capnp:"os"`
 | 
						OS                     string `capnp:"os"`
 | 
				
			||||||
	ExistingTunnelPolicy   tunnelrpc.ExistingTunnelPolicy
 | 
						ExistingTunnelPolicy   tunnelrpc.ExistingTunnelPolicy
 | 
				
			||||||
	PoolID                 string `capnp:"poolId"`
 | 
						PoolID                 string `capnp:"poolId"`
 | 
				
			||||||
	ExposeInternalHostname bool
 | 
					 | 
				
			||||||
	Tags                   []Tag
 | 
						Tags                   []Tag
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,7 +81,7 @@ func UnmarshalServerInfo(s tunnelrpc.ServerInfo) (*ServerInfo, error) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TunnelServer interface {
 | 
					type TunnelServer interface {
 | 
				
			||||||
	RegisterTunnel(ctx context.Context, auth *Authentication, hostname string, options *RegistrationOptions) (*TunnelRegistration, error)
 | 
						RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error)
 | 
				
			||||||
	GetServerInfo(ctx context.Context) (*ServerInfo, error)
 | 
						GetServerInfo(ctx context.Context) (*ServerInfo, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -95,11 +94,7 @@ type TunnelServer_PogsImpl struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (i TunnelServer_PogsImpl) RegisterTunnel(p tunnelrpc.TunnelServer_registerTunnel) error {
 | 
					func (i TunnelServer_PogsImpl) RegisterTunnel(p tunnelrpc.TunnelServer_registerTunnel) error {
 | 
				
			||||||
	authentication, err := p.Params.Auth()
 | 
						originCert, err := p.Params.OriginCert()
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	pogsAuthentication, err := UnmarshalAuthentication(authentication)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -116,7 +111,7 @@ func (i TunnelServer_PogsImpl) RegisterTunnel(p tunnelrpc.TunnelServer_registerT
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	server.Ack(p.Options)
 | 
						server.Ack(p.Options)
 | 
				
			||||||
	registration, err := i.impl.RegisterTunnel(p.Ctx, pogsAuthentication, hostname, pogsOptions)
 | 
						registration, err := i.impl.RegisterTunnel(p.Ctx, originCert, hostname, pogsOptions)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -149,14 +144,10 @@ func (c TunnelServer_PogsClient) Close() error {
 | 
				
			||||||
	return c.Conn.Close()
 | 
						return c.Conn.Close()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c TunnelServer_PogsClient) RegisterTunnel(ctx context.Context, auth *Authentication, hostname string, options *RegistrationOptions) (*TunnelRegistration, error) {
 | 
					func (c TunnelServer_PogsClient) RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error) {
 | 
				
			||||||
	client := tunnelrpc.TunnelServer{Client: c.Client}
 | 
						client := tunnelrpc.TunnelServer{Client: c.Client}
 | 
				
			||||||
	promise := client.RegisterTunnel(ctx, func(p tunnelrpc.TunnelServer_registerTunnel_Params) error {
 | 
						promise := client.RegisterTunnel(ctx, func(p tunnelrpc.TunnelServer_registerTunnel_Params) error {
 | 
				
			||||||
		authentication, err := p.NewAuth()
 | 
							err := p.SetOriginCert(originCert)
 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		err = MarshalAuthentication(authentication, auth)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,8 +11,8 @@ struct Authentication {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct TunnelRegistration {
 | 
					struct TunnelRegistration {
 | 
				
			||||||
    err @0 :Text;
 | 
					    err @0 :Text;
 | 
				
			||||||
    # A list of URLs that the tunnel is accessible from.
 | 
					    # the url to access the tunnel
 | 
				
			||||||
    urls @1 :List(Text);
 | 
					    url @1 :Text;
 | 
				
			||||||
    # Used to inform the client of actions taken.
 | 
					    # Used to inform the client of actions taken.
 | 
				
			||||||
    logLines @2 :List(Text);
 | 
					    logLines @2 :List(Text);
 | 
				
			||||||
    # In case of error, whether the client should attempt to reconnect.
 | 
					    # In case of error, whether the client should attempt to reconnect.
 | 
				
			||||||
| 
						 | 
					@ -29,10 +29,8 @@ struct RegistrationOptions {
 | 
				
			||||||
    existingTunnelPolicy @3 :ExistingTunnelPolicy;
 | 
					    existingTunnelPolicy @3 :ExistingTunnelPolicy;
 | 
				
			||||||
    # If using the balancing policy, identifies the LB pool to use.
 | 
					    # If using the balancing policy, identifies the LB pool to use.
 | 
				
			||||||
    poolId @4 :Text;
 | 
					    poolId @4 :Text;
 | 
				
			||||||
    # Prevents the tunnel from being accessed at <subdomain>.cftunnel.com
 | 
					 | 
				
			||||||
    exposeInternalHostname @5 :Bool;
 | 
					 | 
				
			||||||
    # Client-defined tags to associate with the tunnel
 | 
					    # Client-defined tags to associate with the tunnel
 | 
				
			||||||
    tags @6 :List(Tag);
 | 
					    tags @5 :List(Tag);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Tag {
 | 
					struct Tag {
 | 
				
			||||||
| 
						 | 
					@ -51,6 +49,6 @@ struct ServerInfo {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface TunnelServer {
 | 
					interface TunnelServer {
 | 
				
			||||||
    registerTunnel @0 (auth :Authentication, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
 | 
					    registerTunnel @0 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
 | 
				
			||||||
    getServerInfo @1 () -> (result :ServerInfo);
 | 
					    getServerInfo @1 () -> (result :ServerInfo);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -157,29 +157,23 @@ func (s TunnelRegistration) SetErr(v string) error {
 | 
				
			||||||
	return s.Struct.SetText(0, v)
 | 
						return s.Struct.SetText(0, v)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s TunnelRegistration) Urls() (capnp.TextList, error) {
 | 
					func (s TunnelRegistration) Url() (string, error) {
 | 
				
			||||||
	p, err := s.Struct.Ptr(1)
 | 
						p, err := s.Struct.Ptr(1)
 | 
				
			||||||
	return capnp.TextList{List: p.List()}, err
 | 
						return p.Text(), err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s TunnelRegistration) HasUrls() bool {
 | 
					func (s TunnelRegistration) HasUrl() bool {
 | 
				
			||||||
	p, err := s.Struct.Ptr(1)
 | 
						p, err := s.Struct.Ptr(1)
 | 
				
			||||||
	return p.IsValid() || err != nil
 | 
						return p.IsValid() || err != nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s TunnelRegistration) SetUrls(v capnp.TextList) error {
 | 
					func (s TunnelRegistration) UrlBytes() ([]byte, error) {
 | 
				
			||||||
	return s.Struct.SetPtr(1, v.List.ToPtr())
 | 
						p, err := s.Struct.Ptr(1)
 | 
				
			||||||
 | 
						return p.TextBytes(), err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewUrls sets the urls field to a newly
 | 
					func (s TunnelRegistration) SetUrl(v string) error {
 | 
				
			||||||
// allocated capnp.TextList, preferring placement in s's segment.
 | 
						return s.Struct.SetText(1, v)
 | 
				
			||||||
func (s TunnelRegistration) NewUrls(n int32) (capnp.TextList, error) {
 | 
					 | 
				
			||||||
	l, err := capnp.NewTextList(s.Struct.Segment(), n)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return capnp.TextList{}, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = s.Struct.SetPtr(1, l.List.ToPtr())
 | 
					 | 
				
			||||||
	return l, err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s TunnelRegistration) LogLines() (capnp.TextList, error) {
 | 
					func (s TunnelRegistration) LogLines() (capnp.TextList, error) {
 | 
				
			||||||
| 
						 | 
					@ -349,14 +343,6 @@ func (s RegistrationOptions) SetPoolId(v string) error {
 | 
				
			||||||
	return s.Struct.SetText(3, v)
 | 
						return s.Struct.SetText(3, v)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s RegistrationOptions) ExposeInternalHostname() bool {
 | 
					 | 
				
			||||||
	return s.Struct.Bit(16)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (s RegistrationOptions) SetExposeInternalHostname(v bool) {
 | 
					 | 
				
			||||||
	s.Struct.SetBit(16, v)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (s RegistrationOptions) Tags() (Tag_List, error) {
 | 
					func (s RegistrationOptions) Tags() (Tag_List, error) {
 | 
				
			||||||
	p, err := s.Struct.Ptr(4)
 | 
						p, err := s.Struct.Ptr(4)
 | 
				
			||||||
	return Tag_List{List: p.List()}, err
 | 
						return Tag_List{List: p.List()}, err
 | 
				
			||||||
| 
						 | 
					@ -750,29 +736,18 @@ func (s TunnelServer_registerTunnel_Params) String() string {
 | 
				
			||||||
	return str
 | 
						return str
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s TunnelServer_registerTunnel_Params) Auth() (Authentication, error) {
 | 
					func (s TunnelServer_registerTunnel_Params) OriginCert() ([]byte, error) {
 | 
				
			||||||
	p, err := s.Struct.Ptr(0)
 | 
						p, err := s.Struct.Ptr(0)
 | 
				
			||||||
	return Authentication{Struct: p.Struct()}, err
 | 
						return []byte(p.Data()), err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s TunnelServer_registerTunnel_Params) HasAuth() bool {
 | 
					func (s TunnelServer_registerTunnel_Params) HasOriginCert() bool {
 | 
				
			||||||
	p, err := s.Struct.Ptr(0)
 | 
						p, err := s.Struct.Ptr(0)
 | 
				
			||||||
	return p.IsValid() || err != nil
 | 
						return p.IsValid() || err != nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s TunnelServer_registerTunnel_Params) SetAuth(v Authentication) error {
 | 
					func (s TunnelServer_registerTunnel_Params) SetOriginCert(v []byte) error {
 | 
				
			||||||
	return s.Struct.SetPtr(0, v.Struct.ToPtr())
 | 
						return s.Struct.SetData(0, v)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewAuth sets the auth field to a newly
 | 
					 | 
				
			||||||
// allocated Authentication struct, preferring placement in s's segment.
 | 
					 | 
				
			||||||
func (s TunnelServer_registerTunnel_Params) NewAuth() (Authentication, error) {
 | 
					 | 
				
			||||||
	ss, err := NewAuthentication(s.Struct.Segment())
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return Authentication{}, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = s.Struct.SetPtr(0, ss.Struct.ToPtr())
 | 
					 | 
				
			||||||
	return ss, err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s TunnelServer_registerTunnel_Params) Hostname() (string, error) {
 | 
					func (s TunnelServer_registerTunnel_Params) Hostname() (string, error) {
 | 
				
			||||||
| 
						 | 
					@ -844,10 +819,6 @@ func (p TunnelServer_registerTunnel_Params_Promise) Struct() (TunnelServer_regis
 | 
				
			||||||
	return TunnelServer_registerTunnel_Params{s}, err
 | 
						return TunnelServer_registerTunnel_Params{s}, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (p TunnelServer_registerTunnel_Params_Promise) Auth() Authentication_Promise {
 | 
					 | 
				
			||||||
	return Authentication_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (p TunnelServer_registerTunnel_Params_Promise) Options() RegistrationOptions_Promise {
 | 
					func (p TunnelServer_registerTunnel_Params_Promise) Options() RegistrationOptions_Promise {
 | 
				
			||||||
	return RegistrationOptions_Promise{Pipeline: p.Pipeline.GetPipeline(2)}
 | 
						return RegistrationOptions_Promise{Pipeline: p.Pipeline.GetPipeline(2)}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1060,74 +1031,73 @@ func (p TunnelServer_getServerInfo_Results_Promise) Result() ServerInfo_Promise
 | 
				
			||||||
	return ServerInfo_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
 | 
						return ServerInfo_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const schema_db8274f9144abc7e = "x\xda\x94To\x88\x14e\x18\x7f~\xef\xbb3\xabp" +
 | 
					const schema_db8274f9144abc7e = "x\xda\x9cT_h\x1c\xd5\x1b\xfd\xce\xbd3\xbb-$" +
 | 
				
			||||||
	"\xb6;\xcc\x0aut\x08b\x90\x82\xe6e\x86\x99\xb4\xe7" +
 | 
						"\xbf\xcd0\x1bh\x17B\xa0\xe4\x87\xb6\xd0?\xb1*5" +
 | 
				
			||||||
	"\xa5\xe6^\xa7\xb7\xaf]a\xe9\x07\xc7\xbd\xf7\xf6\xc6f" +
 | 
						"\x167\x89m%1m\xf6\xc6Vk\x9b\x87N7\xb7" +
 | 
				
			||||||
	"g\xb6\x99\xd9\xcb\x8b\xd4\x92 \x0c2R\xfb\xd2\x87\xc8" +
 | 
						"\x9b\x89\xb33\xeb\xccl\xb4\x85\xb4Z\"R\xc1\xa2\x96" +
 | 
				
			||||||
	"\xfbf`%\x14E\x18\x9c\xd0\x1f\xc1\"\x02\x0b\xad\xeb" +
 | 
						"\x82\x05\x11\x15\x0b*E\xfb\xa0h\xa0\x0f\xf5E\x91\"" +
 | 
				
			||||||
	"C\x98\x04RH\xd2\x87\x0cb\xe2\x9d\xbd\xd9\x99\xee\x94" +
 | 
						"\xfa\xa0\x82\xd8\x17-E,J!\xf8\xe2\xd3\xc8\x9d\xcd" +
 | 
				
			||||||
	"\xf0\xdb\xfb\xe7y\x7f\xef\xef\xf9=\xcf\xf3[9\xc1\xfa" +
 | 
						"\xec\x8c)\xa6\xd2\xb7;w\xbe?\xe7\x9c\xfb}g\xcb" +
 | 
				
			||||||
	"X\xafv\x8fF$\xd6iz\xb4\xae\xf1\xcd\xe4\xfdo" +
 | 
						"\xd3l\x90\xf5\xeb\x9bu\"\xb1]\xcfE\xdb\xeb\xdf\xbc" +
 | 
				
			||||||
	"\x9c{\x89\x8c\"\x8b\xf6\x9f\x1e(]\x0f\x0f\xfeH\x84" +
 | 
						"s\xff\xd9+\xf3d\x94Xt\xfc\xd2h\xf1\xaf\xf0\xe4" +
 | 
				
			||||||
	"U\xfb\xd82\x98\xaf\xb2<\x91y\x88\x0d\x11\xa2\x85\x15" +
 | 
						"OD\xd8:\xc7\x8e\xc1|\x95\xe5\x89\xcc\x97\xd98!" +
 | 
				
			||||||
	"LO\xf5\xe6>\"\xa3\x07D\x1a\xcf\x13\xad:\xce\xde" +
 | 
						"\xea\x1e\xc1\xd5\xcb\xfd\xda\xa7d\xdc\x05\"\x9d\xe7\x89\xb6" +
 | 
				
			||||||
	"\x04\xc1<\xc5\xde#D=\xbf\xf7/p\xaf\x1e\x9c\"" +
 | 
						"\x9eg7@0\x17\xd8G\x84\xa8\xe7\x8f\xe1N\xf7\xe6" +
 | 
				
			||||||
	"\xa3\x88\x14*\x0e4\xb7\xf0\xbf\xcd'\xe3\xd5\xe3\\\xc5" +
 | 
						"\xc9\xcbd\x94\x90\x96j\x05>\xc9Ga\xd6\xd5\xd1\xb4" +
 | 
				
			||||||
	"\x0e\xec8zD\xbb|\xf4K\x12Ed\x835\x85\xfa" +
 | 
						"\xb9\x0a\x1e=x\xe65\xfd\xfa\x99/I\x94\x90\x8d\xd6" +
 | 
				
			||||||
	"\x07_\x0c\x139\xb5\xfc\x87\xbf\x06B\xd4\xfd\xfe\x83\xef" +
 | 
						"U\xb4\xae\xf90\xd7j\xea\xd8\xad=\x01BT\xba\xf8" +
 | 
				
			||||||
	"\xf6\x8f\\<7\x0b:fwB\x9b4O\xa9w\xe6" +
 | 
						"\xe0\x87\xc3S?^YV;\x867\xa7/\x9a\xa7T" +
 | 
				
			||||||
	"I\xedYB\xc4.[w\xbc\xf0\xfdC\xd3m\x9e1" +
 | 
						"\x9e\xf9\x82\xfe\x0c!b\xd7\xad\xb5\xcf\xfd\xf0\xd0\xd5\x16" +
 | 
				
			||||||
	"\xca|\xfd\x08(\x17m}b\xc7\x9e\xf9\xfb.]\x9a" +
 | 
						"\xd0\xb8\xca\xcf\xfa/ -\xda\xf3\xf8\xc1\x99\xd5s\xd7" +
 | 
				
			||||||
	"\xc9\x00\xea\xea\xba\x16g0_/\x13\xa2\xd5\xbb\xd6\xcb" +
 | 
						"\xae-Q\x80\xfa\xf5\xbd\x1eS\xf8M/\x13\xa2\xfb\x0e" +
 | 
				
			||||||
	"\x9dk\xb6_!\xa3\xc8\xb3b\x98K\xf5+\xe6j]" +
 | 
						"\x0d\xc9\xc9m\xfbo\x90Q\xe2\xffPcun\x00\xe6" +
 | 
				
			||||||
	"\xfd\xd1\xab\xbfl\x1eR\xab\xe8\xf0\xfe\x0dC\x0f,>" +
 | 
						"\xda\x9cj\xd2\x9d{\xd1\xac\xabSt\xfa\xf8\x8e\xf1\x07" +
 | 
				
			||||||
	"s-\x8b\xf6\x8c>\xa9\xd0^\x8c\xd1F\xd7\xfc\xf6\xc8" +
 | 
						"\xd6}\xbe\x98-\xb7/\xb7\xa8\xca\xd99U\xee\xc8\xb6" +
 | 
				
			||||||
	"]\x87\xbf\xb86\x8b\xb4\x0a4\x8f\xeb?\x98'c\xc0" +
 | 
						"\xdf\x1f\xf9\xff\xe9/\x16\x97\xa1\x8e\x03O\xe56\xc0<" +
 | 
				
			||||||
	"\x13*\xf6\xea\xa6\xb7\xcew\x17\xba\xff\x9c\xa5F\xac\xf1" +
 | 
						"\x17W<\xab\x82o\xeez\xf3\xbbR\xa1\xf4\xe72=" +
 | 
				
			||||||
	"\xd7z7\xcc\x9f\xe2\xd8\x8b\xfa\xaf\xb4<\x0a[\xae+" +
 | 
						"b\xf5\x16r30\xbf\x8ec\xbf\xca\xfdJ\x1b\xa3\xb0" +
 | 
				
			||||||
	"\x1d\xbf\xc9k+jV\xd3m\xae\xdd\xb8\xd7\x0eB\xdb" +
 | 
						"\xe9\xba\xd2\xf1\x1bZusr\xacn\xaaZ\x0d\xb71" +
 | 
				
			||||||
	"\xad\x0f\xc7\x17U\xaf\xe0\xd8\xb5\x89* \xba\xc0\x88\x8c" +
 | 
						"\xb0\xf3Y;\x08m\xb7\xb67\xbe/W<\xc7\xae\x1e" +
 | 
				
			||||||
	"\x9e\xb5D\x80\xb1\xf0)\"0\xc3\xe8'*\xdbu\xd7" +
 | 
						"\xad\x00\xa2\x03\x8c\xc8\xe8\x19 \x02\x8c\xee\x03D`\x86" +
 | 
				
			||||||
	"\xf3e4b\x075\xcfu%\xf1Zx`\xb7\xe5X" +
 | 
						"1LT\xb6k\xae\xe7\xcbh\xca\x0e\xaa\x9e\xebJ\xe2" +
 | 
				
			||||||
	"nMv\xe0\xb5\x04\xbe\x0d\xfb\x98\xf4\xc7\xa5\xbf\xc2\x97" +
 | 
						"\xd5\xf0\xc4a\xcb\xb1\xdc\xaal7\xca\xdd\xda\xa8\xd5\xe0" +
 | 
				
			||||||
	"u;\x08\xa5\xdf>\\R\xb5|\x8b7\x02\xd1\xc5s" +
 | 
						"1\xe9\xcfJ\x7f\x93/kv\x10J\xbfu\xd9W\xb1" +
 | 
				
			||||||
	"D9\x10\x19\x1b\x97\x11\x89>\x0e1\xc8`\x00%\xa8" +
 | 
						"\x0a\xbeU\x0fD\x07\xd7\x884\x10\x19;\x0f\x10\x89\x1d" +
 | 
				
			||||||
	"\xc3\xca\x00\x91\xd8\xcc!\x86\x19\x0c\xc6J1/\xd1O" +
 | 
						"\x1c\xa2\xc2`\x00E\xa8\xcb\xdd\xa3Db\x8cC\xecg" +
 | 
				
			||||||
	"$\x069\xc4v\x86\x82\xd5\x0a\xc7PL{\x88\x80\"" +
 | 
						"0\x18+\xc6\x08\xf7\x0d\x13\x89\x0a\x87\x98d\x88<\xdf" +
 | 
				
			||||||
	"!\x1a\xf3\x82\xd0\xb5\x1a\x92\x88\xd0E\x0c]\x84\x03^" +
 | 
						"\xae\xd9\xee\xc3\x92\xb8\x1f\xa2\x93\x18:\x09\xd1\xb4\x17\x84" +
 | 
				
			||||||
	"3\xb4=7@1\xed\xa2\x99\xe8\x84:K\xa8\xafo" +
 | 
						"\xaeU\x97D\x84\x0eb\xe8 \x9c\xf0\x1a\xa1\xed\xb9\x01" +
 | 
				
			||||||
	"\x85c\xd2\x0d\xedr\xcdRobMR\xa6\x8bo\xc4" +
 | 
						"\xba\xd2\xc9\"\xa0\x8b\xb0\x92VC\xcdpZ\xba\xa1]" +
 | 
				
			||||||
	"\xf4^\"\xb1\x81CT3L\xb7\xecN\x99\xe6\x9f\x96" +
 | 
						"\xb5T2Q,S\x0ay\x1d\x91\x18\xe4\x10c\x19\xc8" +
 | 
				
			||||||
	"\x13\x09\x95E\xb2a\xd9N\xb2\x8b<\xdf\xae\xdb\xee\xc3" +
 | 
						"#\xf7dx$\x90w\x1fNy\xe4\x9f\x92G\x13T" +
 | 
				
			||||||
	"\xeb)\xffh\x1a3\xb7\\\xdbb\x09\xfd\x98\xd1P3" +
 | 
						"\xbd\xb2n\xd9N\xf2\x95\x90\x19\xa2\xfc\xa3i\xccJ\xf8" +
 | 
				
			||||||
	"\xb4\xf3\x9e\x1b(fwv\x98}\xa8\xe4\xfa\x80CL" +
 | 
						"&bU\xfd\x18\xddx\xa37f\xa80\xaeic<" +
 | 
				
			||||||
	"e\x98}\xaa\xe4\xfa\x98C|\x96av\xa6\x9bH\x9c" +
 | 
						"\xa7\x14|\x9dC\xbc\x9b\xc1\xf8\xb6R\xf0\x0d\x0e\xf1^" +
 | 
				
			||||||
	"\xe6\x10g\x19\xc0K\xe0D\xc6\xe7\xef\x10\x89\xb3\x1c\xe2" +
 | 
						"\x06\xe3\xf9\x12\x91x\x8bC\\`\x00/\x82\x13\x19\xef" +
 | 
				
			||||||
	"<\x83\x91\xe3%\xe4\x88\x8co\xd7\x12\x89\xaf8\xc4\x05" +
 | 
						"\x7f@$.p\x88\xcf\x18\x0c\x8d\x17\xa1\x11\x19\x9f\x0c" +
 | 
				
			||||||
	"\x06C+\x96\xa0\x11\x19\xdf}B$.p\x88_\x18" +
 | 
						"\x10\x89\x8b\x1c\xe2\x12\x83\xa1kE\xe8D\xc6\xc2\x06\"" +
 | 
				
			||||||
	"\x0c=W\x82Nd\xfc\xac\x0a8\xcd!\xfeb\x88j" +
 | 
						"\xf11\x87\xf8\x96!\xaa:\xb6t\xc3\x91\xa9\xac\xfe\xb3" +
 | 
				
			||||||
	"\x8e-\xdd\xb02\x92\xd5\x7f\\\xfa\x81\xed\xb9\xc9\x9e{" +
 | 
						"\xd2\x0fl\xcfM\xbe\xb9\x17\xb4\x09\xca\xa5\x89Dk8" +
 | 
				
			||||||
	"A'W9\xd3\x89\xf8O+\xa2\x90\xda\x0c\x01\x05B" +
 | 
						"*^A\x8d$\x0a\xa9\xf7\x10P \x94\x1b\x9e\xe7\x8c" +
 | 
				
			||||||
	"\xb9\xe9yNe$\xf3\xae\xe9\x05\xb2\xe2\"\x94\xbek" +
 | 
						"L%y\x85\xd0\xaa\x05\xf8\x1f\xa1\xc2\x81\xae\xd4\x01\x08" +
 | 
				
			||||||
	"9\x9b\xbdr\xbb\xec\x001\x80P\x08\xadz\x80\xdb\x08" +
 | 
						"\xea\xb2-\x1b[.[oc`\xafUS2\xadj" +
 | 
				
			||||||
	"U\x0e\x14S; \xa8\xc3\x8e\xc4H$\xce\x0f[u" +
 | 
						"\xcb\xb4^\xc1\xef\xe3\x10[22mTOy7\x87" +
 | 
				
			||||||
	"%\xe9\xbc\x8e\xa4KUVK8\xc4\xca\x8c\xa4\xcbU" +
 | 
						"\xb8\x97\xa1\xa0\xe6\xa9\xfdl\xb3\x96\xd3\x94\xb7<\xd0\xed" +
 | 
				
			||||||
	"\xb1\xef\xe6\x10\xf71\x14\xe2\xff\x92\xc2\x8e[NK\xce" +
 | 
						"v\xa0&\xc3\xd6i\xc4=\xe2\xf5U,?o\xd5\x83" +
 | 
				
			||||||
	")\xe1\x8dG\xa2.\xc3\xf6\xaa\xe2\x8ez\xf1D4\x10" +
 | 
						";\xcc\x9e\x90A\xa1\xe9\x84\x81\xd0\xda\x1c:\xd5\xbb\xac" +
 | 
				
			||||||
	"\xdc\xd2\x9bm2h9<\x0cD\xae\xc3w\x81\xaa\xd7" +
 | 
						"\xe2\x10E\x86\xb2/\x83\xa6\x13\xa2+\xb5\x98e\xd3\xce" +
 | 
				
			||||||
	"<\x0eQb(\xfb\xea>D1\xb5\x94\x9b5|\xf2" +
 | 
						"\xff\xad]\xb9\xd5\xa5\xa5\x8fN\xd4\xf6u$vf\xf4" +
 | 
				
			||||||
	"IAa\xb7\x15\xd0\x88:\xde\x8d\xc4\xb4\x8c\xde\xe7\x88" +
 | 
						"\x1f#f\xac\xcf#\xb5R$\xcei\xf4\xf8\xc4\x8c\xee" +
 | 
				
			||||||
	"\x19K\xf3H\xfd\x12\x89=\x1a=>1ca>J" +
 | 
						"|\x94,<\x95[e\x07\x11%\x0c\xa87\xe60\x88" +
 | 
				
			||||||
	"f\x9d\xcam\xd8>D\x09oZ\x143\xefC\x15\xb8" +
 | 
						"\x0ap\xa7\x062!{\x83\xff\xc2?q\xcd\xdb\xb3o" +
 | 
				
			||||||
	"5\xc7P\xb9\xe6\x9d\xff\xcf5\xb1\xc4\x9be\x9a\xc8\xc7" +
 | 
						"\xf5)(d\x8a{\xa6\xee\x0c\x91\xe8\xe0\x10k\x18\"" +
 | 
				
			||||||
	"G=\x95g\x06m\x0f\x91\xe8\xe2\x10\xb73D\x8e\xd7" +
 | 
						"\xc7[\xf2\x82\xc2\x9e\xcc@\xac\xb4\xa3-\xc0\xc9\xa6\x16" +
 | 
				
			||||||
	"\x9e|*l\xcd\x94w\xeeL\xb6\xc9\xa5\x93\xc9\xdbf" +
 | 
						"T\xb2\xaa\xdf\xd5\xaeo)\x1b\x99\xe4\x10\xd3\x99\xd9\x93" +
 | 
				
			||||||
	"Q\xec\xa0Z\xca,vr\x88\xb1L\xffH\xd5T\xbb" +
 | 
						"\xea\xf2\x10\x87p2+j\xabe\x9e\xe6\x10\xf3\xe9\x8a" +
 | 
				
			||||||
	"8\xc4\xf3\x99\x91\x9cP\xc3\xbb\x97C\x1cKG\xf2\xf5" +
 | 
						">\xff\x12\x91\x98\xe7\x10\xaf0\xe4\xa5\xef'\x90\xf2M" +
 | 
				
			||||||
	"W\x88\xc41\x0e\xf16C^\xfa~B\xa4\xd0\xf2\x9d" +
 | 
						"?5\x16\xc7\xab\x8d\xd9\xae\x0c\xd4B.-\x8c\xfa\xa5" +
 | 
				
			||||||
	"N_\xab3\xd5\xcd\x8eW\x1f\xb4]\x19\xa8\x99\x9bu" +
 | 
						"\xd6\xa4!\xfd\xba\xe5J\x17\xe1.\xcbv\x9a\xbeT\x83" +
 | 
				
			||||||
	"\xd5\x94~\xc3r\xa5\x8bp\x93e;-_\xaaFh" +
 | 
						"B\x0c \xfc\x1d\x00\x00\xff\xff\xfa.\x1fg"
 | 
				
			||||||
	"\x8f\xc8\xbf\x01\x00\x00\xff\xff\x80\xf4\x060"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
	schemas.Register(schema_db8274f9144abc7e,
 | 
						schemas.Register(schema_db8274f9144abc7e,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue