forked from kiali/kiali
-
Notifications
You must be signed in to change notification settings - Fork 0
/
kiali.go
314 lines (269 loc) · 9.27 KB
/
kiali.go
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
// Kiali
//
// Kiali project, observability for the Istio service mesh
//
// Schemes: http, https
// BasePath: /api
// Version: _
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// swagger:meta
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/golang/glog"
"github.com/kiali/kiali/config"
"github.com/kiali/kiali/config/security"
"github.com/kiali/kiali/log"
"github.com/kiali/kiali/prometheus/internalmetrics"
"github.com/kiali/kiali/server"
"github.com/kiali/kiali/status"
"github.com/kiali/kiali/util"
)
// Identifies the build. These are set via ldflags during the build (see Makefile).
var (
version = "unknown"
commitHash = "unknown"
)
// Command line arguments
var (
argConfigFile = flag.String("config", "", "Path to the YAML configuration file. If not specified, environment variables will be used for configuration.")
)
func init() {
// log everything to stderr so that it can be easily gathered by logs, separate log files are problematic with containers
_ = flag.Set("logtostderr", "true")
}
func main() {
defer glog.Flush()
util.Clock = util.RealClock{}
// process command line
flag.Parse()
validateFlags()
// log startup information
log.Infof("Kiali: Version: %v, Commit: %v\n", version, commitHash)
log.Debugf("Kiali: Command line: [%v]", strings.Join(os.Args, " "))
// load config file if specified, otherwise, rely on environment variables to configure us
if *argConfigFile != "" {
c, err := config.LoadFromFile(*argConfigFile)
if err != nil {
glog.Fatal(err)
}
config.Set(c)
} else {
log.Infof("No configuration file specified. Will rely on environment for configuration.")
config.Set(config.NewConfig())
}
log.Tracef("Kiali Configuration:\n%s", config.Get())
if err := validateConfig(); err != nil {
glog.Fatal(err)
}
consoleVersion := determineConsoleVersion()
log.Infof("Kiali: Console version: %v", consoleVersion)
status.Put(status.ConsoleVersion, consoleVersion)
status.Put(status.CoreVersion, version)
status.Put(status.CoreCommitHash, commitHash)
status.Put(status.ContainerVersion, determineContainerVersion(version))
if webRoot := config.Get().Server.WebRoot; webRoot != "/" {
updateBaseURL(webRoot)
configToJS()
}
// prepare our internal metrics so Prometheus can scrape them
internalmetrics.RegisterInternalMetrics()
// Start listening to requests
server := server.NewServer()
server.Start()
// wait for the secret when a secret is required
if config.Get().Auth.Strategy == config.AuthStrategyLogin {
waitForSecret()
}
// wait forever, or at least until we are told to exit
waitForTermination()
// Shutdown internal components
log.Info("Shutting down internal components")
server.Stop()
}
// CheckLDAPConfiguration is to check if the required configuration is there in the LDAP configuration
func CheckLDAPConfiguration(auth config.AuthConfig) bool {
if auth.LDAP.LDAPHost == "" || auth.LDAP.LDAPPort == 0 ||
auth.LDAP.LDAPBindDN == "" || auth.LDAP.LDAPBase == "" {
return false
}
return true
}
func waitForSecret() {
foundSecretChan := make(chan security.Credentials)
go func() {
errs := 0
for {
username, uErr := ioutil.ReadFile(config.LoginSecretUsername)
passphrase, pErr := ioutil.ReadFile(config.LoginSecretPassphrase)
if uErr == nil && pErr == nil {
if string(username) != "" && string(passphrase) != "" {
log.Info("Secret is now available.")
foundSecretChan <- security.Credentials{
Username: string(username),
Passphrase: string(passphrase),
}
break
}
}
errs++
if (errs % 5) == 0 {
log.Warning("Kiali is missing a secret that contains both 'username' and 'passphrase'")
}
time.Sleep(2 * time.Second)
}
}()
secret := <-foundSecretChan
cfg := config.Get()
cfg.Server.Credentials.Username = secret.Username
cfg.Server.Credentials.Passphrase = secret.Passphrase
config.Set(cfg)
}
func waitForTermination() {
// Channel that is notified when we are done and should exit
// TODO: may want to make this a package variable - other things might want to tell us to exit
var doneChan = make(chan bool)
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
go func() {
for range signalChan {
log.Info("Termination Signal Received")
doneChan <- true
}
}()
<-doneChan
}
func validateConfig() error {
if config.Get().Server.Port < 0 {
return fmt.Errorf("server port is negative: %v", config.Get().Server.Port)
}
if err := config.Get().Server.Credentials.ValidateCredentials(); err != nil {
return fmt.Errorf("server credentials are invalid: %v", err)
}
if strings.Contains(config.Get().Server.StaticContentRootDirectory, "..") {
return fmt.Errorf("server static content root directory must not contain '..': %v", config.Get().Server.StaticContentRootDirectory)
}
if _, err := os.Stat(config.Get().Server.StaticContentRootDirectory); os.IsNotExist(err) {
return fmt.Errorf("server static content root directory does not exist: %v", config.Get().Server.StaticContentRootDirectory)
}
validPathRegEx := regexp.MustCompile(`^\/[a-zA-Z0-9\-\._~!\$&\'()\*\+\,;=:@%/]*$`)
webRoot := config.Get().Server.WebRoot
if !validPathRegEx.MatchString(webRoot) {
return fmt.Errorf("web root must begin with a / and contain valid URL path characters: %v", webRoot)
}
if webRoot != "/" && strings.HasSuffix(webRoot, "/") {
return fmt.Errorf("web root must not contain a trailing /: %v", webRoot)
}
if strings.Contains(webRoot, "/../") {
return fmt.Errorf("for security purposes, web root must not contain '/../': %v", webRoot)
}
// log some messages to let the administrator know when credentials are configured certain ways
auth := config.Get().Auth
log.Infof("Using authentication strategy [%v]", auth.Strategy)
if auth.Strategy == config.AuthStrategyLogin {
creds := config.Get().Server.Credentials
if creds.Username == "" && creds.Passphrase == "" {
// This won't cause Kiali to abort, but users won't be able to log in, so immediately log a warning
log.Warningf("Credentials are missing. Create a proper secret. Please refer to the documentation for more details.")
}
} else if auth.Strategy == config.AuthStrategyAnonymous {
log.Warningf("Kiali auth strategy is configured for anonymous access - users will not be authenticated.")
} else if auth.Strategy == config.AuthStrategyLDAP && !CheckLDAPConfiguration(auth) {
return fmt.Errorf("Auth strategy is LDAP but there is no LDAP configuration")
}
// Check the signing key for the JWT token is valid
signingKey := config.Get().LoginToken.SigningKey
if err := config.ValidateSigningKey(signingKey, auth.Strategy); err != nil {
return err
}
return nil
}
func validateFlags() {
if *argConfigFile != "" {
if _, err := os.Stat(*argConfigFile); err != nil {
if os.IsNotExist(err) {
log.Debugf("Configuration file [%v] does not exist.", *argConfigFile)
}
}
}
}
// determineConsoleVersion will return the version of the UI console the server will serve to clients.
// Note this method requires the configuration to be loaded and available via config.Get()
func determineConsoleVersion() string {
consoleVersion := "unknown"
filename := config.Get().Server.StaticContentRootDirectory + "/version.txt"
fileContent, err := ioutil.ReadFile(filename)
if err == nil {
consoleVersion = string(fileContent)
consoleVersion = strings.TrimSpace(consoleVersion) // also seems to kill off EOF
} else {
log.Errorf("Failed to determine console version from file [%v]. error=%v", filename, err)
}
return consoleVersion
}
// determineContainerVersion will return the version of the image container.
// It does this by looking at an ENV defined in the Dockerfile when the image is built.
// If the ENV is not defined, the version is assumed the same as the given default value.
func determineContainerVersion(defaultVersion string) string {
v := os.Getenv("KIALI_CONTAINER_VERSION")
if v == "" {
return defaultVersion
}
return v
}
// configToJS generates env.js file from Kiali config
func configToJS() {
log.Info("Generating env.js from config")
path, _ := filepath.Abs("./console/env.js")
content := "window.WEB_ROOT='" + config.Get().Server.WebRoot + "';"
log.Debugf("The content of %v will be:\n%v", path, content)
err := ioutil.WriteFile(path, []byte(content), 0)
if isError(err) {
return
}
}
// updateBaseURL updates index.html base href with web root string
func updateBaseURL(webRootPath string) {
if webRootPath == "/" {
return // nothing to do - our web root path is already /
}
log.Infof("Updating base URL in index.html with [%v]", webRootPath)
path, _ := filepath.Abs("./console/index.html")
b, err := ioutil.ReadFile(path)
if isError(err) {
return
}
html := string(b)
searchStr := `<base href="/"`
newStr := `<base href="` + webRootPath + `/"`
newHTML := strings.Replace(html, searchStr, newStr, -1)
if html != newHTML && strings.Contains(newHTML, newStr) {
log.Debugf("Base URL has been updated to [%v]", newStr)
} else {
log.Warningf("Base URL was not updated [%v]! The custom context root is not in force", searchStr)
}
err = ioutil.WriteFile(path, []byte(newHTML), 0)
if isError(err) {
return
}
}
func isError(err error) bool {
if err != nil {
log.Errorf("File I/O error [%v]", err.Error())
}
return (err != nil)
}