Merge commit 'a7a9d7d' into v0.12

* commit 'a7a9d7d':
  Return correct content type for /rest/events
  Rename RawAPIKey -> APIKey in GUIConfiguration
  Add -paths option to print config, key, database paths
  Clean up error handling a bit in protocol.readMessage
  Remove old reference to moved protocol
  Support multiple API keys (command-line and config) (fixes #2747)
This commit is contained in:
Jakob Borg 2016-02-08 17:38:52 +01:00
commit 194a8b0922
9 changed files with 55 additions and 46 deletions

View File

@ -238,7 +238,7 @@ func (s *apiService) Serve() {
// Wrap everything in CSRF protection. The /rest prefix should be
// protected, other requests will grant cookies.
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg.APIKey(), mux)
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg, mux)
// Add the CORS handling
handler = corsMiddleware(handler)
@ -893,8 +893,10 @@ func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request) {
s.fss.gotEventRequest()
// Flush before blocking, to indicate that we've received the request
// and that it should not be retried.
// Flush before blocking, to indicate that we've received the request and
// that it should not be retried. Must set Content-Type header before
// flushing.
w.Header().Set("Content-Type", "application/json; charset=utf-8")
f := w.(http.Flusher)
f.Flush()

View File

@ -33,9 +33,8 @@ func emitLoginAttempt(success bool, username string) {
}
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
apiKey := cfg.APIKey()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if apiKey != "" && r.Header.Get("X-API-Key") == apiKey {
if cfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
next.ServeHTTP(w, r)
return
}

View File

@ -13,6 +13,7 @@ import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/sync"
)
@ -30,11 +31,11 @@ const maxCsrfTokens = 25
// Check for CSRF token on /rest/ URLs. If a correct one is not given, reject
// the request with 403. For / and /index.html, set a new CSRF cookie if none
// is currently set.
func csrfMiddleware(unique, prefix, apiKey string, next http.Handler) http.Handler {
func csrfMiddleware(unique string, prefix string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
loadCsrfTokens()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Allow requests carrying a valid API key
if apiKey != "" && r.Header.Get("X-API-Key") == apiKey {
if cfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
next.ServeHTTP(w, r)
return
}

View File

@ -122,11 +122,6 @@ var (
const (
usage = "syncthing [options]"
extraUsage = `
The default configuration directory is:
%s
The -logflags value is a sum of the following:
1 Date
@ -199,6 +194,7 @@ type RuntimeOptions struct {
confDir string
reset bool
showVersion bool
showPaths bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
@ -260,6 +256,7 @@ func parseCommandLineOptions() RuntimeOptions {
flag.BoolVar(&options.doUpgrade, "upgrade", false, "Perform upgrade")
flag.BoolVar(&options.doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
flag.BoolVar(&options.showVersion, "version", false, "Show version")
flag.BoolVar(&options.showPaths, "paths", false, "Show configuration paths")
flag.StringVar(&options.upgradeTo, "upgrade-to", options.upgradeTo, "Force upgrade directly from specified URL")
flag.BoolVar(&options.auditEnabled, "audit", false, "Write events to audit file")
flag.BoolVar(&options.verbose, "verbose", false, "Print verbose log output")
@ -270,7 +267,7 @@ func parseCommandLineOptions() RuntimeOptions {
flag.BoolVar(&options.hideConsole, "no-console", false, "Hide console window")
}
longUsage := fmt.Sprintf(extraUsage, baseDirs["config"], debugFacilities())
longUsage := fmt.Sprintf(extraUsage, debugFacilities())
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
flag.Parse()
@ -320,6 +317,11 @@ func main() {
return
}
if options.showPaths {
showPaths()
return
}
if options.browserOnly {
openGUI()
return
@ -478,7 +480,7 @@ func upgradeViaRest() error {
cfg, _ := loadConfig()
target := cfg.GUI().URL()
r, _ := http.NewRequest("POST", target+"/rest/system/upgrade", nil)
r.Header.Set("X-API-Key", cfg.GUI().APIKey())
r.Header.Set("X-API-Key", cfg.GUI().APIKey)
tr := &http.Transport{
Dial: dialer.Dial,
@ -1216,3 +1218,13 @@ func checkShortIDs(cfg *config.Wrapper) error {
}
return nil
}
func showPaths() {
fmt.Printf("Configuration file:\n\t%s\n\n", locations[locConfigFile])
fmt.Printf("Database directory:\n\t%s\n\n", locations[locDatabase])
fmt.Printf("Device private key & certificate files:\n\t%s\n\t%s\n\n", locations[locKeyFile], locations[locCertFile])
fmt.Printf("HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", locations[locHTTPSKeyFile], locations[locHTTPSCertFile])
fmt.Printf("Log file:\n\t%s\n\n", locations[locLogFile])
fmt.Printf("GUI override directory:\n\t%s\n\n", locations[locGUIAssets])
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations[locDefFolder])
}

View File

@ -229,8 +229,8 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
cfg.Options.ReconnectIntervalS = 5
}
if cfg.GUI.RawAPIKey == "" {
cfg.GUI.RawAPIKey = randomString(32)
if cfg.GUI.APIKey == "" {
cfg.GUI.APIKey = randomString(32)
}
}

View File

@ -485,7 +485,7 @@ func TestCopy(t *testing.T) {
cfg.Devices[0].Addresses[0] = "wrong"
cfg.Folders[0].Devices[0].DeviceID = protocol.DeviceID{0, 1, 2, 3}
cfg.Options.ListenAddress[0] = "wrong"
cfg.GUI.RawAPIKey = "wrong"
cfg.GUI.APIKey = "wrong"
bsChanged, err := json.MarshalIndent(cfg, "", " ")
if err != nil {

View File

@ -18,7 +18,7 @@ type GUIConfiguration struct {
User string `xml:"user,omitempty" json:"user"`
Password string `xml:"password,omitempty" json:"password"`
RawUseTLS bool `xml:"tls,attr" json:"useTLS"`
RawAPIKey string `xml:"apikey,omitempty" json:"apiKey"`
APIKey string `xml:"apikey,omitempty" json:"apiKey"`
InsecureAdminAccess bool `xml:"insecureAdminAccess,omitempty" json:"insecureAdminAccess"`
Theme string `xml:"theme" json:"theme" default:"default"`
}
@ -76,9 +76,17 @@ func (c GUIConfiguration) URL() string {
return u.String()
}
func (c GUIConfiguration) APIKey() string {
if override := os.Getenv("STGUIAPIKEY"); override != "" {
return override
// IsValidAPIKey returns true when the given API key is valid, including both
// the value in config and any overrides
func (c GUIConfiguration) IsValidAPIKey(apiKey string) bool {
switch apiKey {
case "":
return false
case c.APIKey, os.Getenv("STGUIAPIKEY"):
return true
default:
return false
}
return c.RawAPIKey
}

View File

@ -430,36 +430,20 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
}
}
// We check each returned error for the XDRError.IsEOF() method.
// IsEOF()==true here means that the message contained fewer fields than
// expected. It does not signify an EOF on the socket, because we've
// successfully read a size value and that many bytes already. New fields
// we expected but the other peer didn't send should be interpreted as
// zero/nil, and if that's not valid we'll verify it somewhere else.
switch hdr.msgType {
case messageTypeIndex, messageTypeIndexUpdate:
var idx IndexMessage
err = idx.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = idx
case messageTypeRequest:
var req RequestMessage
err = req.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = req
case messageTypeResponse:
var resp ResponseMessage
err = resp.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = resp
case messageTypePing:
@ -468,23 +452,28 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
case messageTypeClusterConfig:
var cc ClusterConfigMessage
err = cc.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = cc
case messageTypeClose:
var cm CloseMessage
err = cm.UnmarshalXDR(msgBuf)
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
msg = cm
default:
err = fmt.Errorf("protocol error: %s: unknown message type %#x", c.id, hdr.msgType)
}
// We check the returned error for the XDRError.IsEOF() method.
// IsEOF()==true here means that the message contained fewer fields than
// expected. It does not signify an EOF on the socket, because we've
// successfully read a size value and then that many bytes from the wire.
// New fields we expected but the other peer didn't send should be
// interpreted as zero/nil, and if that's not valid we'll verify it
// somewhere else.
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
err = nil
}
return
}

View File

@ -1,2 +0,0 @@
Syncthing uses the protocols defined in
https://github.com/syncthing/specs/.