trafficcontrol-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dewr...@apache.org
Subject [2/4] incubator-trafficcontrol git commit: Add experimental to-go-monitoring authentication
Date Wed, 05 Jul 2017 18:36:58 GMT
Add experimental to-go-monitoring authentication


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/e540e36d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/e540e36d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/e540e36d

Branch: refs/heads/master
Commit: e540e36d2a83678aa129049e3e6a35d83bccddd3
Parents: 56eebd2
Author: Robert Butts <robert.o.butts@gmail.com>
Authored: Thu Mar 23 22:10:36 2017 -0600
Committer: Dewayne Richardson <dewrich@apache.org>
Committed: Wed Jul 5 12:36:53 2017 -0600

----------------------------------------------------------------------
 .../go-monitoring/to-go-monitoring.go           | 68 ++++++++++++++----
 traffic_ops/experimental/tocookie/tocookie.go   | 76 ++++++++++++++++++++
 2 files changed, 129 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/e540e36d/traffic_ops/experimental/go-monitoring/to-go-monitoring.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/go-monitoring/to-go-monitoring.go b/traffic_ops/experimental/go-monitoring/to-go-monitoring.go
index 9a2ade6..a508212 100644
--- a/traffic_ops/experimental/go-monitoring/to-go-monitoring.go
+++ b/traffic_ops/experimental/go-monitoring/to-go-monitoring.go
@@ -17,6 +17,7 @@ import (
 	"encoding/json"
 	"flag"
 	"fmt"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/experimental/tocookie"
 	"github.com/lib/pq"
 	"net/http"
 	"strings"
@@ -40,6 +41,8 @@ type Args struct {
 	DBServer string
 	DBDB     string
 	DBSSL    bool
+	Auth     bool
+	TOSecret string
 }
 
 // getFlags parses and returns the command line arguments. The returned error
@@ -52,6 +55,8 @@ func getFlags() (Args, error) {
 	flag.StringVar(&args.DBServer, "server", "", "the database server IP or FQDN, without
scheme")
 	flag.StringVar(&args.DBDB, "db", "", "the database name")
 	flag.BoolVar(&args.DBSSL, "ssl", true, "whether to require or disable SSL connecting
to the database")
+	flag.StringVar(&args.TOSecret, "secret", "", "the Traffic Ops secret, used to authenticate
mojolicious cookies")
+	flag.BoolVar(&args.Auth, "authenticate", true, "whether to authenticate requests, requiring
valid Traffic Ops cookies")
 	flag.Parse()
 	if args.HTTPPort == "" {
 		return args, fmt.Errorf("missing port")
@@ -68,6 +73,9 @@ func getFlags() (Args, error) {
 	if args.DBDB == "" {
 		return args, fmt.Errorf("missing database")
 	}
+	if args.Auth && args.TOSecret == "" {
+		return args, fmt.Errorf("missing secret")
+	}
 	return args, nil
 }
 
@@ -116,12 +124,12 @@ type Profile struct {
 }
 
 type Monitoring struct {
-	TrafficServers   []Cache           `json:trafficServers`
-	TrafficMonitors  []Monitor         `json:trafficMonitors`
-	Cachegroups      []Cachegroup      `json:cacheGroups`
-	Profiles         []Profile         `json:profiles`
-	DeliveryServices []DeliveryService `json:deliveryServices`
-	Config           map[string]string `json:config`
+	TrafficServers   []Cache           `json:"trafficServers"`
+	TrafficMonitors  []Monitor         `json:"trafficMonitors"`
+	Cachegroups      []Cachegroup      `json:"cacheGroups"`
+	Profiles         []Profile         `json:"profiles"`
+	DeliveryServices []DeliveryService `json:"deliveryServices"`
+	Config           map[string]string `json:"config"`
 }
 
 type MonitoringResponse struct {
@@ -428,30 +436,60 @@ func getMonitoringJson(cdnName string, db *sql.DB) (*MonitoringResponse,
error)
 	return &resp, nil
 }
 
-func rootHandler(w http.ResponseWriter, r *http.Request, db *sql.DB) {
-	handleErr := func(err error) {
+// authenticate attempts to authenticate the given request, looking for an auth cookie, using
the auth settings in args. Returns nil if authentication succeeds, or a descriptive error
if authentication fails (which should NOT be returned to clients, for security reasons).
+func authenticate(r *http.Request, args Args) error {
+	if !args.Auth {
+		return nil
+	}
+
+	cookie, err := r.Cookie(tocookie.Name)
+	if err != nil {
+		return fmt.Errorf("getting cookie: %v", err)
+	}
+	if cookie == nil {
+		return fmt.Errorf("no auth cookie")
+	}
+
+	if _, err := tocookie.Parse(args.TOSecret, cookie.Value); err != nil {
+		return fmt.Errorf("parsing cookie: %v", err)
+	}
+	return nil
+}
+
+func rootHandler(w http.ResponseWriter, r *http.Request, db *sql.DB, args Args) {
+	start := time.Now()
+	defer func() {
+		now := time.Now()
+		fmt.Printf("%v %v served %v in %v\n", now, r.RemoteAddr, r.URL.Path, now.Sub(start))
+	}()
+
+	handleErr := func(err error, status int) {
 		fmt.Printf("%v %v error %v\n", time.Now(), r.RemoteAddr, err)
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprintf(w, "Internal Server Error")
+		w.WriteHeader(status)
+		fmt.Fprintf(w, http.StatusText(status))
+	}
+
+	if err := authenticate(r, args); err != nil {
+		handleErr(err, http.StatusUnauthorized)
+		return
 	}
 
 	pathParts := strings.Split(r.URL.Path, "/")
 	if len(pathParts) < 5 {
-		w.WriteHeader(http.StatusNotFound)
-		fmt.Fprintf(w, "404 Not Found")
+		handleErr(fmt.Errorf("nonexistent path requested: '%v'", r.URL.Path), http.StatusNotFound)
 		return
 	}
 	cdnName := pathParts[4]
 
 	resp, err := getMonitoringJson(cdnName, db)
 	if err != nil {
-		handleErr(err)
+		handleErr(err, http.StatusInternalServerError)
 		return
 	}
 
 	respBts, err := json.Marshal(resp)
 	if err != nil {
-		handleErr(err)
+		handleErr(err, http.StatusInternalServerError)
 		return
 	}
 
@@ -480,7 +518,7 @@ func main() {
 	defer db.Close()
 
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		rootHandler(w, r, db)
+		rootHandler(w, r, db, args)
 	})
 
 	if err := http.ListenAndServe(":"+args.HTTPPort, nil); err != nil {

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/e540e36d/traffic_ops/experimental/tocookie/tocookie.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/tocookie/tocookie.go b/traffic_ops/experimental/tocookie/tocookie.go
new file mode 100644
index 0000000..3c42d59
--- /dev/null
+++ b/traffic_ops/experimental/tocookie/tocookie.go
@@ -0,0 +1,76 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+// http://www.apache.org/licenses/LICENSE-2.0
+
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tocookie
+
+import (
+	"crypto/hmac"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"strings"
+	"time"
+)
+
+const Name = "mojolicious"
+
+type Cookie struct {
+	AuthData    string `json:"auth_data"`
+	ExpiresUnix int64  `json:"expires"`
+}
+
+func checkHmac(message, messageMAC, key []byte) bool {
+	mac := hmac.New(sha1.New, key)
+	mac.Write(message)
+	expectedMAC := mac.Sum(nil)
+	return hmac.Equal(messageMAC, expectedMAC)
+}
+
+func Parse(secret, cookie string) (*Cookie, error) {
+	dashPos := strings.Index(cookie, "-")
+	if dashPos == -1 {
+		return nil, fmt.Errorf("malformed cookie '%s' - no dashes", cookie)
+	}
+	if len(cookie) < dashPos+4 {
+		return nil, fmt.Errorf("malformed cookie '%s' - no signature", cookie)
+	}
+
+	base64Txt := cookie[:dashPos]
+
+	txtBytes, err := base64.RawURLEncoding.DecodeString(base64Txt)
+	if err != nil {
+		return nil, fmt.Errorf("error decoding base64 data: %v", err)
+	}
+
+	base64Sig := cookie[dashPos+4:]
+	sigBytes, err := hex.DecodeString(base64Sig)
+	if err != nil {
+		return nil, fmt.Errorf("error decoding signature: %v", err)
+	}
+
+	if !checkHmac([]byte(base64Txt+"--"), sigBytes, []byte(secret)) {
+		return nil, fmt.Errorf("bad signature")
+	}
+
+	cookieData := Cookie{}
+	if err := json.Unmarshal(txtBytes, &cookieData); err != nil {
+		return nil, fmt.Errorf("error decoding base64 text '%s' to JSON: %v", string(txtBytes),
err)
+	}
+
+	if cookieData.ExpiresUnix-time.Now().Unix() < 0 {
+		return nil, fmt.Errorf("signature expired")
+	}
+
+	return &cookieData, nil
+}


Mime
View raw message