-
Notifications
You must be signed in to change notification settings - Fork 190
Expand file tree
/
Copy pathmain.go
More file actions
197 lines (170 loc) · 5.98 KB
/
Copy pathmain.go
File metadata and controls
197 lines (170 loc) · 5.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package main
import (
"bytes"
"context"
"flag"
"fmt"
"net/http"
"os"
"strconv"
"time"
"github.com/teslamotors/vehicle-command/internal/log"
"github.com/teslamotors/vehicle-command/pkg/cli"
"github.com/teslamotors/vehicle-command/pkg/protocol"
"github.com/teslamotors/vehicle-command/pkg/proxy"
)
const (
cacheSize = 10000 // Number of cached vehicle sessions
defaultPort = 443
)
const (
EnvTLSCert = "TESLA_HTTP_PROXY_TLS_CERT"
EnvTLSKey = "TESLA_HTTP_PROXY_TLS_KEY"
EnvHost = "TESLA_HTTP_PROXY_HOST"
EnvPort = "TESLA_HTTP_PROXY_PORT"
EnvTimeout = "TESLA_HTTP_PROXY_TIMEOUT"
EnvVerbose = "TESLA_VERBOSE"
)
const nonLocalhostWarning = `
Do not listen on a network interface without adding client authentication. Unauthorized clients may
be used to create excessive traffic from your IP address to Tesla's servers, which Tesla may respond
to by rate limiting or blocking your connections.`
type HTTProxyConfig struct {
keyFilename string
certFilename string
verbose bool
host string
port int
timeout time.Duration
}
var (
httpConfig = &HTTProxyConfig{}
)
func init() {
flag.StringVar(&httpConfig.certFilename, "cert", "", "TLS certificate chain `file` with concatenated server, intermediate CA, and root CA certificates")
flag.StringVar(&httpConfig.keyFilename, "tls-key", "", "Server TLS private key `file`")
flag.BoolVar(&httpConfig.verbose, "verbose", false, "Enable verbose logging")
flag.StringVar(&httpConfig.host, "host", "localhost", "Proxy server `hostname`")
flag.IntVar(&httpConfig.port, "port", defaultPort, "`Port` to listen on")
flag.DurationVar(&httpConfig.timeout, "timeout", proxy.DefaultTimeout, "Timeout interval when sending commands")
}
func Usage() {
out := flag.CommandLine.Output()
fmt.Fprintf(out, "Usage: %s [OPTION...]\n", os.Args[0])
fmt.Fprintf(out, "\nA server that exposes a REST API for sending commands to Tesla vehicles")
fmt.Fprintln(out, "")
fmt.Fprintln(out, nonLocalhostWarning)
fmt.Fprintln(out, "")
fmt.Fprintln(out, "Options:")
flag.PrintDefaults()
}
func main() {
// ******************************************************************************************
// WHY IS THERE NO OPTION FOR DISABLING TLS?
// ******************************************************************************************
// In the past, we have had problems with third-party applications that made it easy for DIY
// enthusiasts to inadvertently expose their vehicles to the public Internet. In order to
// protect users who do not understand the risks of disabling TLS, we decided to omit an
// --insecure flag or similar.
//
// Expert users who need to disable TLS can do so without forking this repository by using the
// pkg/proxy package, which is agnostic to TLS. This application is a very thin wrapper around
// that package.
config, err := cli.NewConfig(cli.FlagPrivateKey)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to load credential configuration: %s\n", err)
os.Exit(1)
}
defer func() {
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
os.Exit(1)
}
}()
flag.Usage = Usage
config.RegisterCommandLineFlags()
flag.Parse()
err = readFromEnvironment()
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading environment: %s\n", err)
os.Exit(1)
}
config.ReadFromEnvironment()
if httpConfig.verbose {
log.SetLevel(log.LevelDebug)
}
if httpConfig.host != "localhost" {
fmt.Fprintln(os.Stderr, nonLocalhostWarning)
}
var skey protocol.ECDHPrivateKey
skey, err = config.PrivateKey()
if err != nil {
return
}
if tlsPublicKey, err := protocol.LoadPublicKey(httpConfig.keyFilename); err == nil {
if bytes.Equal(tlsPublicKey.Bytes(), skey.PublicBytes()) {
fmt.Fprintln(os.Stderr, "It is unsafe to use the same private key for TLS and command authentication.")
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Generate a new TLS key for this server.")
return
}
log.Debug("Verified that TLS key is not the same as the command-authentication key.")
} else {
// Discarding the error here is deliberate
log.Debug("Verified that TLS key is not a recycled command-authentication key, because it is not NIST P256.")
}
log.Debug("Creating proxy")
p, err := proxy.New(context.Background(), skey, cacheSize)
if err != nil {
log.Error("Error initializing proxy service: %v", err)
return
}
p.Timeout = httpConfig.timeout
addr := fmt.Sprintf("%s:%d", httpConfig.host, httpConfig.port)
log.Info("Listening on %s", addr)
// To add more application logic requests, such as alternative client authentication, create
// a http.HandleFunc implementation (https://pkg.go.dev/net/http#HandlerFunc). The ServeHTTP
// method of your implementation can perform your business logic and then, if the request is
// authorized, invoke p.ServeHTTP. Finally, replace p in the below ListenAndServeTLS call with
// an object of your newly created type.
log.Error("Server stopped: %s", http.ListenAndServeTLS(addr, httpConfig.certFilename, httpConfig.keyFilename, p))
}
// readConfig applies configuration from environment variables.
// Values are not overwritten.
func readFromEnvironment() error {
if httpConfig.certFilename == "" {
httpConfig.certFilename = os.Getenv(EnvTLSCert)
}
if httpConfig.keyFilename == "" {
httpConfig.keyFilename = os.Getenv(EnvTLSKey)
}
if httpConfig.host == "localhost" {
host, ok := os.LookupEnv(EnvHost)
if ok {
httpConfig.host = host
}
}
if !httpConfig.verbose {
if verbose, ok := os.LookupEnv(EnvVerbose); ok {
httpConfig.verbose = verbose != "false" && verbose != "0"
}
}
var err error
if httpConfig.port == defaultPort {
if port, ok := os.LookupEnv(EnvPort); ok {
httpConfig.port, err = strconv.Atoi(port)
if err != nil {
return fmt.Errorf("invalid port: %s", port)
}
}
}
if httpConfig.timeout == proxy.DefaultTimeout {
if timeoutEnv, ok := os.LookupEnv(EnvTimeout); ok {
httpConfig.timeout, err = time.ParseDuration(timeoutEnv)
if err != nil {
return fmt.Errorf("invalid timeout: %s", timeoutEnv)
}
}
}
return nil
}