You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by jv...@apache.org on 2017/03/11 18:09:53 UTC
[3/5] incubator-trafficcontrol git commit: original webfront,
auth services taken form https://github.com/rarenivar/project5799
original webfront, auth services taken form https://github.com/rarenivar/project5799
Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/c6221c2e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/c6221c2e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/c6221c2e
Branch: refs/heads/master
Commit: c6221c2eaa10b287ce8c55aec0b5269829971b3e
Parents: cb08fc3
Author: Amir Yeshurun <am...@qwilt.com>
Authored: Sun Feb 26 22:51:53 2017 +0200
Committer: Jan van Doorn <ja...@cable.comcast.com>
Committed: Sat Mar 11 11:06:09 2017 -0700
----------------------------------------------------------------------
traffic_ops/experimental/auth/Dockerfile | 23 ++
traffic_ops/experimental/auth/auth.config | 8 +
traffic_ops/experimental/auth/auth.go | 172 +++++++++++
traffic_ops/experimental/auth/server.key | 27 ++
traffic_ops/experimental/auth/server.pem | 21 ++
traffic_ops/experimental/webfront/Dockerfile | 25 ++
traffic_ops/experimental/webfront/README.md | 98 +++++++
traffic_ops/experimental/webfront/rules.json | 32 ++
traffic_ops/experimental/webfront/server.key | 27 ++
traffic_ops/experimental/webfront/server.pem | 21 ++
.../webfront/simpleserver/server.key | 27 ++
.../webfront/simpleserver/server.pem | 21 ++
.../webfront/simpleserver/simpleserver.go | 25 ++
traffic_ops/experimental/webfront/startfakes.sh | 7 +
.../experimental/webfront/webfront.config | 5 +
traffic_ops/experimental/webfront/webfront.go | 293 +++++++++++++++++++
16 files changed, 832 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/auth/Dockerfile
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/auth/Dockerfile b/traffic_ops/experimental/auth/Dockerfile
new file mode 100644
index 0000000..4ec7a98
--- /dev/null
+++ b/traffic_ops/experimental/auth/Dockerfile
@@ -0,0 +1,23 @@
+# Start from a Debian image with the latest version of Go installed
+# and a workspace (GOPATH) configured at /go.
+FROM golang
+
+# Copy the local package files to the container's workspace.
+# Note: need to move to ~/go?
+ADD . /go/src/github.com/rarenivar/project5799/auth
+
+# Build the outyet command inside the container.
+# (You may fetch or manage dependencies here,
+# either manually or with a tool like "godep".)
+RUN go get github.com/dgrijalva/jwt-go
+RUN go get github.com/jmoiron/sqlx
+RUN go get github.com/lib/pq
+WORKDIR ./src/github.com/rarenivar/project5799/auth
+RUN export GOBIN=$GOPATH/bin && go install auth.go
+
+# Document that the service listens on port 9000.
+EXPOSE 8080
+
+# Run the outyet command by default when the container starts.
+ENTRYPOINT /go/bin/auth auth.config
+
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/auth/auth.config
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/auth/auth.config b/traffic_ops/experimental/auth/auth.config
new file mode 100644
index 0000000..642923e
--- /dev/null
+++ b/traffic_ops/experimental/auth/auth.config
@@ -0,0 +1,8 @@
+{
+ "dbName":"amiry",
+ "dbUser":"amiry",
+ "dbPassword":"blabla",
+ "dbServer":"localhost",
+ "dbPort":5432,
+ "listenerPort":"8080"
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/auth/auth.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/auth/auth.go b/traffic_ops/experimental/auth/auth.go
new file mode 100644
index 0000000..937124f
--- /dev/null
+++ b/traffic_ops/experimental/auth/auth.go
@@ -0,0 +1,172 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ jwt "github.com/dgrijalva/jwt-go"
+ "github.com/jmoiron/sqlx"
+ _ "github.com/lib/pq"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "path"
+ "time"
+)
+
+// Config holds the configuration of the server.
+type Config struct {
+ DbName string `json:"dbName"`
+ DbUser string `json:"dbUser"`
+ DbPassword string `json:"dbPassword"`
+ DbServer string `json:"dbServer,omitempty"`
+ DbPort uint `json:"dbPort,omitempty"`
+ ListenerPort string `json:"listenerPort"`
+}
+
+type User struct {
+ Username string `db:"username" json:"username"`
+ FirstName string `db:"first_name" json:"firstName,omitempty"`
+ LastName string `db:"last_name" json:"lastName,omitempty"`
+ Password string `db:"password" json:"password"`
+}
+
+type Claims struct {
+ Capabilities []string `json:"cap"`
+ jwt.StandardClaims
+}
+
+type TokenResponse struct {
+ Token string
+}
+
+var db *sqlx.DB // global and simple
+
+func printUsage() {
+ exampleConfig := `{
+ "dbName":"my-db",
+ "dbUser":"my-user",
+ "dbPassword":"secret",
+ "dbServer":"localhost",
+ "dbPort":5432,
+ "listenerPort":"8080"
+}`
+ Logger.Println("Usage: " + path.Base(os.Args[0]) + " configfile")
+ Logger.Println("")
+ Logger.Println("Example config file:")
+ Logger.Println(exampleConfig)
+}
+
+var Logger *log.Logger
+
+func main() {
+ if len(os.Args) < 2 {
+ printUsage()
+ return
+ }
+
+ // log.SetOutput(os.Stdout)
+ Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
+
+ file, err := os.Open(os.Args[1])
+ if err != nil {
+ Logger.Println("Error opening config file:", err)
+ return
+ }
+ decoder := json.NewDecoder(file)
+ config := Config{}
+ err = decoder.Decode(&config)
+ if err != nil {
+ Logger.Println("Error reading config file:", err)
+ return
+ }
+
+ db, err = InitializeDatabase(config.DbUser, config.DbPassword, config.DbName, config.DbServer, config.DbPort)
+ if err != nil {
+ Logger.Println("Error initializing database:", err)
+ return
+ }
+
+ http.HandleFunc("/", handler)
+ if _, err := os.Stat("server.pem"); os.IsNotExist(err) {
+ Logger.Fatal("server.pem file not found")
+ }
+ if _, err := os.Stat("server.key"); os.IsNotExist(err) {
+ Logger.Fatal("server.key file not found")
+ }
+ Logger.Printf("Starting server on port " + config.ListenerPort + "...")
+ Logger.Fatal(http.ListenAndServeTLS(":"+config.ListenerPort, "server.pem", "server.key", nil))
+}
+
+func InitializeDatabase(username, password, dbname, server string, port uint) (*sqlx.DB, error) {
+ connString := fmt.Sprintf("host=%s dbname=%s user=%s password=%s sslmode=disable", server, dbname, username, password)
+
+ db, err := sqlx.Connect("postgres", connString)
+ if err != nil {
+ return nil, err
+ }
+
+ return db, nil
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+
+ Logger.Println(r.Method, r.URL.Scheme, r.Host, r.URL.RequestURI())
+
+ if r.Method == "POST" {
+ var u User
+ userlist := []User{}
+ body, err := ioutil.ReadAll(r.Body)
+ log.Println(string(body))
+ if err != nil {
+ Logger.Println("Error reading body: ", err.Error())
+ http.Error(w, "Error reading body: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+ err = json.Unmarshal(body, &u)
+ if err != nil {
+ Logger.Println("Error unmarshalling JSON: ", err.Error())
+ http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+ stmt, err := db.PrepareNamed("SELECT * FROM users WHERE username=:username")
+ err = stmt.Select(&userlist, u)
+ if err != nil {
+ Logger.Println(err.Error())
+ http.Error(w, "Database error: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if len(userlist) == 0 || userlist[0].Password != u.Password {
+ http.Error(w, "Invalid username/password ", http.StatusUnauthorized)
+ return
+ }
+
+ claims := Claims {
+ []string{"read-a", "write-a", "read-b"}, // TODO(amiry) - Read from DB
+ jwt.StandardClaims {
+ Subject: u.Username,
+ ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), // TODO(amiry) - Use shorter expiration
+ },
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+
+ tokenString, err := token.SignedString([]byte("CAmeRAFiveSevenNineNine")) // TODO(amiry) - Where to keep secret?
+ if err != nil {
+ Logger.Println(err.Error())
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ js, err := json.Marshal(TokenResponse{Token: tokenString})
+ if err != nil {
+ Logger.Println(err.Error())
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ // w.WriteHeader(http.StatusOK)
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(js)
+ return
+ }
+ http.Error(w, r.Method+" "+r.URL.Path+" not valid for this microservice", http.StatusNotFound)
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/auth/server.key
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/auth/server.key b/traffic_ops/experimental/auth/server.key
new file mode 100644
index 0000000..b674147
--- /dev/null
+++ b/traffic_ops/experimental/auth/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA0g8TSrCJLfzMxzCB+zLo19NIrOAA2kR25vi8Hu+aM2PGxJbH
+FPSV0LG1HF3l/4QoG1JNYKuyXaPFDtPcefU7fQ7MqpAiBWj2HyIfnKyZyUX867Pg
+2CnSPTgMZyK3NMSnkAG+FSZzXKj6Vxjh0mKN1X+JRqyv1c/rWLg3RxecuVdq0D23
+l/1nZPWidiRp2XhUKeO3L3rLKu9tf80HJZ1TN3TsUxOGUgZHectN577qAaHr3KzD
+HVA4v7fmNRXQEUg4Iax8FxWIw0CQOFY9+lUqW1TKr7UmrqnzmhQmLFNBP5wuU1fI
+yz5HqNeR2VOt10VDBfYg7Wy08mFsSr3bhj9GMwIDAQABAoIBAQCC+H9QzG1bzQlp
+EKealg1zs/rWPvyJGrMAJAo3R7FfZVCjdlc+i5l1e7euriUfgaj4EALKyYL2u4u8
+SQBo0ix7NuUJW8C6ms0KcF5Bc6SXSBoAZDFG1hyNqwqgq5aLQiovscZwrX60QW+F
+KrByWpyQh2pyNG2V5IOa15EvtFs1e1f7+DCZ/KLjd5odxEZ93lLcDMQ4zmSl47uR
+I5rdixCQm8slUtRJxAB+nRck5htVGY8cwT6c+hx/GlZqY6xynMOwa9pC3zFegnr3
+nvHIiFEVu0CksaVBZNRe0S92r8VjHQPVRBa87Tsom6vJ0GxaeDfXchedrklCxMJh
+Y+QOPIVhAoGBAPgDxXGBD7VaUsBgimJKE1Q/inT2aZtwKrJi4l2fRE1Q0kOZvePv
+/LMiUZwcV0M+y45Y0VU2VXIdqAC1tYFhFfRKdmjCABFSidyhpIMJJZQrX3fFEDZq
+Vt95PrjlRC3+AVvVXVNWOxP4ObgorPzqXKtgxt10OltJfiLHHe0LF9nJAoGBANjS
+dUNU9OhCFA54MpfKtNLXvnYsxXigkN9rXAiOt9yd3x4l3o1EJs9t4a6stgzPaXDc
+ANkIPf1xsO9N0c+rikYQRo+JmbTaAQhac19NF7guTV6lHRa9bR4N4O14+62oXFEX
+3z5hp2Tr0/1qjp62ubDR8S7zY8ACsKiqd0msZ94bAoGAEy3XeuuMF24gsBfHG8q2
+q/Et99WGXSrTYnAbKTpDwebaG7gr4xCP7hpdTUEzzlNw0lUz+u70tJpuf3+Nxa+I
+TxfjCD7YWn6TgqhNXIS8jzS7cTugAVU/2pA5tXqlRxk5aayaRvOIJgcwD3m1xuko
+uWgGeOGgEachRfc37TjKY2ECgYAmDMDxYP5dqAL3Cxbt1m/nNF9DQ9YftiXWX3PW
+OJ2BhN773m9w4Q3ihwj862hDVT7OxH0NmUmvqX0AceqpBBSO+Ro45E/qEfvuaFko
+11GbS9zeCCHTtMhqZssbQYkBT7Nrs8HEo8SJjG02YrXGmpB5vE4UDDhsIMy5vLiU
+YEIWEwKBgQCXvBaNGJxPDu3//8yJJC4gFSWUxWYZ9x12Kke6AaUoeEn8RjTlaLRm
+Ol1ZiqHTSbRJQHjqmwKJAWSA3QX4S3ISeVOms7k30Bt02m4W0NgVRB2GxkFcnESH
+OxREDAQXG2lLP9O2HZPPHZ9aKKFM+M0GlOdQlqKk3AHPx33qCXLYtg==
+-----END RSA PRIVATE KEY-----
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/auth/server.pem
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/auth/server.pem b/traffic_ops/experimental/auth/server.pem
new file mode 100644
index 0000000..c09c2ca
--- /dev/null
+++ b/traffic_ops/experimental/auth/server.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDbzCCAlegAwIBAgIJAJozT3F7lmQwMA0GCSqGSIb3DQEBCwUAME4xCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDTzEPMA0GA1UEBwwGRGVudmVyMSEwHwYDVQQKDBhJ
+bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYwMzI2MjIyODExWhcNMjYwMzI0
+MjIyODExWjBOMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xDzANBgNVBAcMBkRl
+bnZlcjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0g8TSrCJLfzMxzCB+zLo19NIrOAA2kR2
+5vi8Hu+aM2PGxJbHFPSV0LG1HF3l/4QoG1JNYKuyXaPFDtPcefU7fQ7MqpAiBWj2
+HyIfnKyZyUX867Pg2CnSPTgMZyK3NMSnkAG+FSZzXKj6Vxjh0mKN1X+JRqyv1c/r
+WLg3RxecuVdq0D23l/1nZPWidiRp2XhUKeO3L3rLKu9tf80HJZ1TN3TsUxOGUgZH
+ectN577qAaHr3KzDHVA4v7fmNRXQEUg4Iax8FxWIw0CQOFY9+lUqW1TKr7Umrqnz
+mhQmLFNBP5wuU1fIyz5HqNeR2VOt10VDBfYg7Wy08mFsSr3bhj9GMwIDAQABo1Aw
+TjAdBgNVHQ4EFgQUdme3OnuvS+09/3Nkx1FS/4xgXoUwHwYDVR0jBBgwFoAUdme3
+OnuvS+09/3Nkx1FS/4xgXoUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
+AQEANNo5qASaoCc5/Utr10KIO/KoFesWL0v0bDIG3r0HaEp2gD44o884yQ8pFDNR
+bhUjs1SaHHcnGX5X3OGJVuP2vuz0pFaeKZJUawT1DTwaXQCTkRFw0Cb7oWdF6/FV
+pXuLBXWvEsu19jnrZGQbJw1lsOchPDNpp/h460JBWQOOYRfu3jSyBMwih40lEb+F
+1lA943aA2oGsDYJHnGRpUMLafe4gqcGWpfHUf2TaGe+tUSI7+JrhYLzjRyqTBdD3
+5Ss+eQo5tP0aOVMMCMCHmFfxgjBHGu4syQW5VYIZtzWMEKu2fEZCjkbV6XwVYVFL
+SKh7EgT1te7yUhfLSAF2969WHg==
+-----END CERTIFICATE-----
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/Dockerfile
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/Dockerfile b/traffic_ops/experimental/webfront/Dockerfile
new file mode 100644
index 0000000..c46e36a
--- /dev/null
+++ b/traffic_ops/experimental/webfront/Dockerfile
@@ -0,0 +1,25 @@
+# Start from a Debian image with the latest version of Go installed
+# and a workspace (GOPATH) configured at /go.
+FROM golang
+
+# Copy the local package files to the container's workspace.
+# Note: need to move to ~/go?
+ADD . /go/src/github.com/rarenivar/project5799/webfront
+
+# Build the outyet command inside the container.
+# (You may fetch or manage dependencies here,
+# either manually or with a tool like "godep".)
+RUN go get github.com/dgrijalva/jwt-go
+#RUN ls -lR
+WORKDIR ./src/github.com/rarenivar/project5799/webfront
+#RUN cd ./src/github.com/rarenivar/project5799/webfront && export GOBIN=$GOPATH/bin && go install webfront.go
+RUN export GOBIN=$GOPATH/bin && go install webfront.go
+#RUN ls -l
+#RUN go install webfront.go
+
+# Document that the service listens on port 9000.
+EXPOSE 9000
+
+# Run the outyet command by default when the container starts.
+ENTRYPOINT /go/bin/webfront webfront.config
+
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/README.md
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/README.md b/traffic_ops/experimental/webfront/README.md
new file mode 100644
index 0000000..041a219
--- /dev/null
+++ b/traffic_ops/experimental/webfront/README.md
@@ -0,0 +1,98 @@
+This application - webfront is a reverse proxy written in go that can front any number of microservices. It uses a rules file to map from requested host/path to microservice host/port/path. Example rule file:
+
+
+ [
+ {"Host": "local.com", "Path" : "/8001", "Forward": "localhost:8001"},
+ {"Host": "local.com", "Path" : "/8002", "Forward": "localhost:8002"},
+ {"Host": "local.com", "Path" : "/8003", "Forward": "localhost:8003"},
+ {"Host": "local.com", "Path" : "/8004", "Forward": "localhost:8004"},
+ {"Host": "local.com", "Path" : "/8005", "Forward": "localhost:8005"},
+ {"Host": "local.com", "Path" : "/8006", "Forward": "localhost:8006"},
+ {"Host": "local.com", "Path" : "/8007", "Forward": "localhost:8007"}
+ ]
+
+
+No restart is needed to re-read the rule file and apply; within 60 seconds of a change in the file, it will pick up the new mappings.
+
+To run
+
+ go run webfront.go -rules=rules.json -https=:9000 -https_cert=server.pem -https_key=server.key
+
+(or compile a binary, and run that)
+
+To get a token:
+
+ curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}'
+
+in my case that returned:
+
+ {"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}``
+
+ Example:
+
+ [jvd@laika webfront (master *=)]$ curl --insecure -Lkvs --header "Content-Type:application/json" -XPOST https://localhost:9000/login -d'{"username":"jvd", "password":"tootoo"}'
+ * Trying ::1...
+ * Connected to localhost (::1) port 9000 (#0)
+ * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ * Server certificate: CU
+ > POST /login HTTP/1.1
+ > Host: localhost:9000
+ > User-Agent: curl/7.43.0
+ > Accept: */*
+ > Content-Type:application/json
+ > Content-Length: 39
+ >
+ * upload completely sent off: 39 out of 39 bytes
+ < HTTP/1.1 200 OK
+ < Content-Type: application/json
+ < Date: Thu, 24 Mar 2016 23:30:19 GMT
+ < Content-Length: 157
+ <
+ * Connection #0 to host localhost left intact
+ {"Token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk"}[jvd@laika webfront (master *=)]$
+
+ * To use a token:
+
+ curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r
+
+Example:
+
+
+ [jvd@laika webfront (master *%=)]$ curl --insecure -H'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk' -Lkvs https://localhost:9000/8003/r
+ * Trying ::1...
+ * Connected to localhost (::1) port 9000 (#0)
+ * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ * Server certificate: CU
+ > GET /8003/r HTTP/1.1
+ > Host: localhost:9000
+ > User-Agent: curl/7.43.0
+ > Accept: */*
+ > Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJQYXNzd29yZCI6InRvb3RvbyIsIlVzZXIiOiIiLCJleHAiOjE0NTg5NDg2MTl9.quCwZ5vghVBucxMxQ4fSfD84yw_yPEp9qLGGQNcHNUk
+ >
+ < HTTP/1.1 200 OK
+ < Content-Length: 24
+ < Content-Type: text/plain; charset=utf-8
+ < Date: Thu, 24 Mar 2016 23:34:08 GMT
+ <
+ Hitting 8003 with /boo1
+ * Connection #0 to host localhost left intact
+ [jvd@laika webfront (master *%=)]$
+
+ [jvd@laika webfront (master=)]$ curl --insecure -H'Authorization: Bearer FAKETOKEN' -Lkvs https://localhost:9000/8003/r * Trying ::1...
+ * Connected to localhost (::1) port 9000 (#0)
+ * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ * Server certificate: CU
+ > GET /8003/r HTTP/1.1
+ > Host: localhost:9000
+ > User-Agent: curl/7.43.0
+ > Accept: */*
+ > Authorization: Bearer FAKETOKEN
+ >
+ < HTTP/1.1 403 Forbidden
+ < Date: Thu, 24 Mar 2016 23:43:11 GMT
+ < Content-Length: 0
+ < Content-Type: text/plain; charset=utf-8
+ <
+ * Connection #0 to host localhost left intact
+ [jvd@laika webfront (master=)]$
+
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/rules.json
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/rules.json b/traffic_ops/experimental/webfront/rules.json
new file mode 100644
index 0000000..fd6e792
--- /dev/null
+++ b/traffic_ops/experimental/webfront/rules.json
@@ -0,0 +1,32 @@
+[
+ {
+ "host": "local.com",
+ "path": "/login",
+ "forward": "localhost:8080",
+ "secure": false
+ },
+ {
+ "host": "local.com",
+ "path": "/a",
+ "forward": "localhost:8081",
+ "secure": true,
+ "capabilities": {
+ "GET": "read-a",
+ "POST": "write-a",
+ "PUT": "write-a",
+ "PATCH": "write-a"
+ }
+ },
+ {
+ "host": "local.com",
+ "path": "/b",
+ "forward": "localhost:8082",
+ "secure": true,
+ "capabilities": {
+ "GET": "read-b",
+ "POST": "write-b",
+ "PUT": "write-b",
+ "PATCH": "write-b"
+ }
+ }
+]
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/server.key
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/server.key b/traffic_ops/experimental/webfront/server.key
new file mode 100644
index 0000000..b674147
--- /dev/null
+++ b/traffic_ops/experimental/webfront/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA0g8TSrCJLfzMxzCB+zLo19NIrOAA2kR25vi8Hu+aM2PGxJbH
+FPSV0LG1HF3l/4QoG1JNYKuyXaPFDtPcefU7fQ7MqpAiBWj2HyIfnKyZyUX867Pg
+2CnSPTgMZyK3NMSnkAG+FSZzXKj6Vxjh0mKN1X+JRqyv1c/rWLg3RxecuVdq0D23
+l/1nZPWidiRp2XhUKeO3L3rLKu9tf80HJZ1TN3TsUxOGUgZHectN577qAaHr3KzD
+HVA4v7fmNRXQEUg4Iax8FxWIw0CQOFY9+lUqW1TKr7UmrqnzmhQmLFNBP5wuU1fI
+yz5HqNeR2VOt10VDBfYg7Wy08mFsSr3bhj9GMwIDAQABAoIBAQCC+H9QzG1bzQlp
+EKealg1zs/rWPvyJGrMAJAo3R7FfZVCjdlc+i5l1e7euriUfgaj4EALKyYL2u4u8
+SQBo0ix7NuUJW8C6ms0KcF5Bc6SXSBoAZDFG1hyNqwqgq5aLQiovscZwrX60QW+F
+KrByWpyQh2pyNG2V5IOa15EvtFs1e1f7+DCZ/KLjd5odxEZ93lLcDMQ4zmSl47uR
+I5rdixCQm8slUtRJxAB+nRck5htVGY8cwT6c+hx/GlZqY6xynMOwa9pC3zFegnr3
+nvHIiFEVu0CksaVBZNRe0S92r8VjHQPVRBa87Tsom6vJ0GxaeDfXchedrklCxMJh
+Y+QOPIVhAoGBAPgDxXGBD7VaUsBgimJKE1Q/inT2aZtwKrJi4l2fRE1Q0kOZvePv
+/LMiUZwcV0M+y45Y0VU2VXIdqAC1tYFhFfRKdmjCABFSidyhpIMJJZQrX3fFEDZq
+Vt95PrjlRC3+AVvVXVNWOxP4ObgorPzqXKtgxt10OltJfiLHHe0LF9nJAoGBANjS
+dUNU9OhCFA54MpfKtNLXvnYsxXigkN9rXAiOt9yd3x4l3o1EJs9t4a6stgzPaXDc
+ANkIPf1xsO9N0c+rikYQRo+JmbTaAQhac19NF7guTV6lHRa9bR4N4O14+62oXFEX
+3z5hp2Tr0/1qjp62ubDR8S7zY8ACsKiqd0msZ94bAoGAEy3XeuuMF24gsBfHG8q2
+q/Et99WGXSrTYnAbKTpDwebaG7gr4xCP7hpdTUEzzlNw0lUz+u70tJpuf3+Nxa+I
+TxfjCD7YWn6TgqhNXIS8jzS7cTugAVU/2pA5tXqlRxk5aayaRvOIJgcwD3m1xuko
+uWgGeOGgEachRfc37TjKY2ECgYAmDMDxYP5dqAL3Cxbt1m/nNF9DQ9YftiXWX3PW
+OJ2BhN773m9w4Q3ihwj862hDVT7OxH0NmUmvqX0AceqpBBSO+Ro45E/qEfvuaFko
+11GbS9zeCCHTtMhqZssbQYkBT7Nrs8HEo8SJjG02YrXGmpB5vE4UDDhsIMy5vLiU
+YEIWEwKBgQCXvBaNGJxPDu3//8yJJC4gFSWUxWYZ9x12Kke6AaUoeEn8RjTlaLRm
+Ol1ZiqHTSbRJQHjqmwKJAWSA3QX4S3ISeVOms7k30Bt02m4W0NgVRB2GxkFcnESH
+OxREDAQXG2lLP9O2HZPPHZ9aKKFM+M0GlOdQlqKk3AHPx33qCXLYtg==
+-----END RSA PRIVATE KEY-----
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/server.pem
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/server.pem b/traffic_ops/experimental/webfront/server.pem
new file mode 100644
index 0000000..c09c2ca
--- /dev/null
+++ b/traffic_ops/experimental/webfront/server.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDbzCCAlegAwIBAgIJAJozT3F7lmQwMA0GCSqGSIb3DQEBCwUAME4xCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDTzEPMA0GA1UEBwwGRGVudmVyMSEwHwYDVQQKDBhJ
+bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYwMzI2MjIyODExWhcNMjYwMzI0
+MjIyODExWjBOMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xDzANBgNVBAcMBkRl
+bnZlcjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0g8TSrCJLfzMxzCB+zLo19NIrOAA2kR2
+5vi8Hu+aM2PGxJbHFPSV0LG1HF3l/4QoG1JNYKuyXaPFDtPcefU7fQ7MqpAiBWj2
+HyIfnKyZyUX867Pg2CnSPTgMZyK3NMSnkAG+FSZzXKj6Vxjh0mKN1X+JRqyv1c/r
+WLg3RxecuVdq0D23l/1nZPWidiRp2XhUKeO3L3rLKu9tf80HJZ1TN3TsUxOGUgZH
+ectN577qAaHr3KzDHVA4v7fmNRXQEUg4Iax8FxWIw0CQOFY9+lUqW1TKr7Umrqnz
+mhQmLFNBP5wuU1fIyz5HqNeR2VOt10VDBfYg7Wy08mFsSr3bhj9GMwIDAQABo1Aw
+TjAdBgNVHQ4EFgQUdme3OnuvS+09/3Nkx1FS/4xgXoUwHwYDVR0jBBgwFoAUdme3
+OnuvS+09/3Nkx1FS/4xgXoUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
+AQEANNo5qASaoCc5/Utr10KIO/KoFesWL0v0bDIG3r0HaEp2gD44o884yQ8pFDNR
+bhUjs1SaHHcnGX5X3OGJVuP2vuz0pFaeKZJUawT1DTwaXQCTkRFw0Cb7oWdF6/FV
+pXuLBXWvEsu19jnrZGQbJw1lsOchPDNpp/h460JBWQOOYRfu3jSyBMwih40lEb+F
+1lA943aA2oGsDYJHnGRpUMLafe4gqcGWpfHUf2TaGe+tUSI7+JrhYLzjRyqTBdD3
+5Ss+eQo5tP0aOVMMCMCHmFfxgjBHGu4syQW5VYIZtzWMEKu2fEZCjkbV6XwVYVFL
+SKh7EgT1te7yUhfLSAF2969WHg==
+-----END CERTIFICATE-----
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/simpleserver/server.key
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/simpleserver/server.key b/traffic_ops/experimental/webfront/simpleserver/server.key
new file mode 100644
index 0000000..b674147
--- /dev/null
+++ b/traffic_ops/experimental/webfront/simpleserver/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA0g8TSrCJLfzMxzCB+zLo19NIrOAA2kR25vi8Hu+aM2PGxJbH
+FPSV0LG1HF3l/4QoG1JNYKuyXaPFDtPcefU7fQ7MqpAiBWj2HyIfnKyZyUX867Pg
+2CnSPTgMZyK3NMSnkAG+FSZzXKj6Vxjh0mKN1X+JRqyv1c/rWLg3RxecuVdq0D23
+l/1nZPWidiRp2XhUKeO3L3rLKu9tf80HJZ1TN3TsUxOGUgZHectN577qAaHr3KzD
+HVA4v7fmNRXQEUg4Iax8FxWIw0CQOFY9+lUqW1TKr7UmrqnzmhQmLFNBP5wuU1fI
+yz5HqNeR2VOt10VDBfYg7Wy08mFsSr3bhj9GMwIDAQABAoIBAQCC+H9QzG1bzQlp
+EKealg1zs/rWPvyJGrMAJAo3R7FfZVCjdlc+i5l1e7euriUfgaj4EALKyYL2u4u8
+SQBo0ix7NuUJW8C6ms0KcF5Bc6SXSBoAZDFG1hyNqwqgq5aLQiovscZwrX60QW+F
+KrByWpyQh2pyNG2V5IOa15EvtFs1e1f7+DCZ/KLjd5odxEZ93lLcDMQ4zmSl47uR
+I5rdixCQm8slUtRJxAB+nRck5htVGY8cwT6c+hx/GlZqY6xynMOwa9pC3zFegnr3
+nvHIiFEVu0CksaVBZNRe0S92r8VjHQPVRBa87Tsom6vJ0GxaeDfXchedrklCxMJh
+Y+QOPIVhAoGBAPgDxXGBD7VaUsBgimJKE1Q/inT2aZtwKrJi4l2fRE1Q0kOZvePv
+/LMiUZwcV0M+y45Y0VU2VXIdqAC1tYFhFfRKdmjCABFSidyhpIMJJZQrX3fFEDZq
+Vt95PrjlRC3+AVvVXVNWOxP4ObgorPzqXKtgxt10OltJfiLHHe0LF9nJAoGBANjS
+dUNU9OhCFA54MpfKtNLXvnYsxXigkN9rXAiOt9yd3x4l3o1EJs9t4a6stgzPaXDc
+ANkIPf1xsO9N0c+rikYQRo+JmbTaAQhac19NF7guTV6lHRa9bR4N4O14+62oXFEX
+3z5hp2Tr0/1qjp62ubDR8S7zY8ACsKiqd0msZ94bAoGAEy3XeuuMF24gsBfHG8q2
+q/Et99WGXSrTYnAbKTpDwebaG7gr4xCP7hpdTUEzzlNw0lUz+u70tJpuf3+Nxa+I
+TxfjCD7YWn6TgqhNXIS8jzS7cTugAVU/2pA5tXqlRxk5aayaRvOIJgcwD3m1xuko
+uWgGeOGgEachRfc37TjKY2ECgYAmDMDxYP5dqAL3Cxbt1m/nNF9DQ9YftiXWX3PW
+OJ2BhN773m9w4Q3ihwj862hDVT7OxH0NmUmvqX0AceqpBBSO+Ro45E/qEfvuaFko
+11GbS9zeCCHTtMhqZssbQYkBT7Nrs8HEo8SJjG02YrXGmpB5vE4UDDhsIMy5vLiU
+YEIWEwKBgQCXvBaNGJxPDu3//8yJJC4gFSWUxWYZ9x12Kke6AaUoeEn8RjTlaLRm
+Ol1ZiqHTSbRJQHjqmwKJAWSA3QX4S3ISeVOms7k30Bt02m4W0NgVRB2GxkFcnESH
+OxREDAQXG2lLP9O2HZPPHZ9aKKFM+M0GlOdQlqKk3AHPx33qCXLYtg==
+-----END RSA PRIVATE KEY-----
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/simpleserver/server.pem
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/simpleserver/server.pem b/traffic_ops/experimental/webfront/simpleserver/server.pem
new file mode 100644
index 0000000..c09c2ca
--- /dev/null
+++ b/traffic_ops/experimental/webfront/simpleserver/server.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDbzCCAlegAwIBAgIJAJozT3F7lmQwMA0GCSqGSIb3DQEBCwUAME4xCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDTzEPMA0GA1UEBwwGRGVudmVyMSEwHwYDVQQKDBhJ
+bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYwMzI2MjIyODExWhcNMjYwMzI0
+MjIyODExWjBOMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xDzANBgNVBAcMBkRl
+bnZlcjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0g8TSrCJLfzMxzCB+zLo19NIrOAA2kR2
+5vi8Hu+aM2PGxJbHFPSV0LG1HF3l/4QoG1JNYKuyXaPFDtPcefU7fQ7MqpAiBWj2
+HyIfnKyZyUX867Pg2CnSPTgMZyK3NMSnkAG+FSZzXKj6Vxjh0mKN1X+JRqyv1c/r
+WLg3RxecuVdq0D23l/1nZPWidiRp2XhUKeO3L3rLKu9tf80HJZ1TN3TsUxOGUgZH
+ectN577qAaHr3KzDHVA4v7fmNRXQEUg4Iax8FxWIw0CQOFY9+lUqW1TKr7Umrqnz
+mhQmLFNBP5wuU1fIyz5HqNeR2VOt10VDBfYg7Wy08mFsSr3bhj9GMwIDAQABo1Aw
+TjAdBgNVHQ4EFgQUdme3OnuvS+09/3Nkx1FS/4xgXoUwHwYDVR0jBBgwFoAUdme3
+OnuvS+09/3Nkx1FS/4xgXoUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
+AQEANNo5qASaoCc5/Utr10KIO/KoFesWL0v0bDIG3r0HaEp2gD44o884yQ8pFDNR
+bhUjs1SaHHcnGX5X3OGJVuP2vuz0pFaeKZJUawT1DTwaXQCTkRFw0Cb7oWdF6/FV
+pXuLBXWvEsu19jnrZGQbJw1lsOchPDNpp/h460JBWQOOYRfu3jSyBMwih40lEb+F
+1lA943aA2oGsDYJHnGRpUMLafe4gqcGWpfHUf2TaGe+tUSI7+JrhYLzjRyqTBdD3
+5Ss+eQo5tP0aOVMMCMCHmFfxgjBHGu4syQW5VYIZtzWMEKu2fEZCjkbV6XwVYVFL
+SKh7EgT1te7yUhfLSAF2969WHg==
+-----END CERTIFICATE-----
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/simpleserver/simpleserver.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/simpleserver/simpleserver.go b/traffic_ops/experimental/webfront/simpleserver/simpleserver.go
new file mode 100644
index 0000000..e4baeb2
--- /dev/null
+++ b/traffic_ops/experimental/webfront/simpleserver/simpleserver.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "io"
+ "log"
+ "net/http"
+ "os"
+)
+
+var Logger *log.Logger
+
+func hello(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, "Hitting "+os.Args[1]+" with "+r.URL.Path+"\n")
+}
+
+func main() {
+ Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
+
+ http.HandleFunc("/", hello)
+
+ // Make sure you have the server.pem and server.key file. To gen self signed:
+ // openssl genrsa -out server.key 2048
+ // openssl req -new -x509 -key server.key -out server.pem -days 3650
+ Logger.Println(http.ListenAndServeTLS(":"+os.Args[1], "server.pem", "server.key", nil))
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/startfakes.sh
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/startfakes.sh b/traffic_ops/experimental/webfront/startfakes.sh
new file mode 100644
index 0000000..66675e5
--- /dev/null
+++ b/traffic_ops/experimental/webfront/startfakes.sh
@@ -0,0 +1,7 @@
+go run simpleserver/simpleserver.go 8001 &
+go run simpleserver/simpleserver.go 8002 &
+go run simpleserver/simpleserver.go 8003 &
+go run simpleserver/simpleserver.go 8004 &
+go run simpleserver/simpleserver.go 8005 &
+go run simpleserver/simpleserver.go 8006 &
+go run simpleserver/simpleserver.go 8007
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/webfront.config
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/webfront.config b/traffic_ops/experimental/webfront/webfront.config
new file mode 100644
index 0000000..f487dbd
--- /dev/null
+++ b/traffic_ops/experimental/webfront/webfront.config
@@ -0,0 +1,5 @@
+{
+ "httpsAddr": ":9000",
+ "ruleFile": "rules.json",
+ "pollInterval": 60
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c6221c2e/traffic_ops/experimental/webfront/webfront.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/webfront/webfront.go b/traffic_ops/experimental/webfront/webfront.go
new file mode 100644
index 0000000..76be08d
--- /dev/null
+++ b/traffic_ops/experimental/webfront/webfront.go
@@ -0,0 +1,293 @@
+// Started with https://github.com/nf/webfront/blob/master/main.go
+// by Andrew Gerrand <ad...@golang.org>
+
+package main
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ jwt "github.com/dgrijalva/jwt-go"
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "os"
+ "path"
+ "strings"
+ "sync"
+ "time"
+)
+
+// TODO(amiry) - Handle token expiration
+// TODO(amiry) - Handle refresh tokens
+// TODO(amiry) - Where to keep the jwt secret? In the command line
+
+// Server implements an http.Handler that acts as a reverse proxy
+type Server struct {
+ mu sync.RWMutex // guards the fields below
+ last time.Time
+ rules []*Rule
+}
+
+// Rule represents a rule in a configuration file.
+type Rule struct {
+ Host string // to match against request Host header
+ Path string // to match against a path (start)
+ Forward string // reverse proxy map-to
+ Secure bool // protect with jwt?
+ Capabilities map[string]string // map HTTP methods to capabilitues
+
+ handler http.Handler
+}
+
+type Claims struct {
+ Capabilities []string `json:"cap"`
+ jwt.StandardClaims
+}
+
+// Config holds the configuration of the server.
+type Config struct {
+ HTTPSAddr string `json:"httpsAddr"`
+ RuleFile string `json:"ruleFile"`
+ PollInterval int `json:"pollInterval"`
+}
+
+var Logger *log.Logger
+
+func printUsage() {
+ exampleConfig := `{
+ "httpsAddr": ":9000",
+ "ruleFile": "rules.json",
+ "pollInterval": 60
+}`
+ fmt.Println("Usage: " + path.Base(os.Args[0]) + " config-file")
+ fmt.Println("")
+ fmt.Println("Example config-file:")
+ fmt.Println(exampleConfig)
+}
+
+func main() {
+
+ if len(os.Args) < 2 {
+ printUsage()
+ return
+ }
+
+ Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
+
+ file, err := os.Open(os.Args[1])
+ if err != nil {
+ Logger.Println("Error opening config file:", err)
+ return
+ }
+ decoder := json.NewDecoder(file)
+ config := Config{}
+ err = decoder.Decode(&config)
+ if err != nil {
+ Logger.Println("Error reading config file:", err)
+ return
+ }
+ Logger.Println("Starting webfront...")
+ s, err := NewServer(config.RuleFile, time.Duration(config.PollInterval)*time.Second)
+ if err != nil {
+ Logger.Fatal(err)
+ }
+
+ // override the default so we can use self-signed certs on our microservices
+ // and use a self-signed cert in this server
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+ if _, err := os.Stat("server.pem"); os.IsNotExist(err) {
+ Logger.Fatal("server.pem file not found")
+ }
+ if _, err := os.Stat("server.key"); os.IsNotExist(err) {
+ Logger.Fatal("server.key file not found")
+ }
+ http.ListenAndServeTLS(config.HTTPSAddr, "server.pem", "server.key", s)
+}
+
+func validateToken(tokenString string) (*jwt.Token, error) {
+
+ tokenString = strings.Replace(tokenString, "Bearer ", "", 1)
+ token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
+ }
+ return []byte("CAmeRAFiveSevenNineNine"), nil
+ })
+ return token, err
+}
+
+// NewServer constructs a Server that reads rules from file with a period
+// specified by poll.
+func NewServer(file string, poll time.Duration) (*Server, error) {
+ s := new(Server)
+ if err := s.loadRules(file); err != nil {
+ Logger.Fatal("Error loading rules file: ", err)
+ }
+ go s.refreshRules(file, poll)
+ return s, nil
+}
+
+// ServeHTTP matches the Request with a Rule and, if found, serves the
+// request with the Rule's handler. If the rule's secure field is true, it will
+// only allow access if the request has a valid JWT bearer token.
+func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+
+ rule := s.getRule(r)
+ if rule == nil {
+ Logger.Printf("%v %v No mapping in rules file!", r.Method, r.URL.RequestURI())
+ http.Error(w, "Not found", http.StatusNotFound)
+ return
+ }
+
+ isAuthorized := false
+
+ if rule.Secure {
+ tokenValid := false
+ token, err := validateToken(r.Header.Get("Authorization"))
+
+ if err == nil {
+ tokenValid = true
+ } else {
+ Logger.Println("Token Error:", err.Error())
+ }
+
+ if !tokenValid {
+ Logger.Printf("%v %v Valid token required, but none found!", r.Method, r.URL.RequestURI())
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+
+ claims, ok := token.Claims.(*Claims)
+ if !ok {
+ Logger.Printf("%v %v Valid token found, but cannot parse claims!", r.Method, r.URL.RequestURI())
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+
+ // Authorization: Check is the list of capabilities in the token's claims contains the reqired capability
+ // that is listed in the rule
+ Logger.Printf("Required capabilities %v", rule.Capabilities)
+ for _, c := range claims.Capabilities {
+ if c == rule.Capabilities[r.Method] {
+ isAuthorized = true
+ break
+ }
+ }
+
+ Logger.Printf("%v %v Valid token. Subject=%v, ExpiresAt=%v, Capabilities=%v, Required=%v, Authorized=%v",
+ r.Method, r.URL.RequestURI(), claims.Subject, claims.ExpiresAt, claims.Capabilities,
+ rule.Capabilities[r.Method], isAuthorized)
+
+ } else {
+ isAuthorized = true
+ }
+
+ if isAuthorized {
+ if h := rule.handler; h != nil {
+ h.ServeHTTP(w, r)
+ return
+ }
+ }
+
+ http.Error(w, "Not Authorized", http.StatusUnauthorized)
+ return
+}
+
+func rejectNoToken(handler http.Handler) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusForbidden)
+ }
+}
+
+func (s *Server) getRule(req *http.Request) *Rule {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ h := req.Host
+ p := req.URL.Path
+
+ // Some clients include a port in the request host; strip it.
+ if i := strings.Index(h, ":"); i >= 0 {
+ h = h[:i]
+ }
+
+ for _, r := range s.rules {
+ if strings.HasPrefix(p, r.Path) {
+ // Logger.Printf("Found rule")
+ return r
+ }
+ }
+
+ // Logger.Printf("Rule not found")
+ return nil
+}
+
+// refreshRules polls file periodically and refreshes the Server's rule
+// set if the file has been modified.
+func (s *Server) refreshRules(file string, poll time.Duration) {
+ for {
+ // Logger.Printf("loading rule file")
+ if err := s.loadRules(file); err != nil {
+ Logger.Println(file, ":", err)
+ }
+ time.Sleep(poll)
+ }
+}
+
+// loadRules tests whether file has been modified since its last invocation
+// and, if so, loads the rule set from file.
+func (s *Server) loadRules(file string) error {
+ fi, err := os.Stat(file)
+ if err != nil {
+ return err
+ }
+ mtime := fi.ModTime()
+ if !mtime.After(s.last) && s.rules != nil {
+ return nil // no change
+ }
+ rules, err := parseRules(file)
+ if err != nil {
+ return err
+ }
+ s.mu.Lock()
+ s.last = mtime
+ s.rules = rules
+ s.mu.Unlock()
+ return nil
+}
+
+// parseRules reads rule definitions from file, constructs the Rule handlers,
+// and returns the resultant Rules.
+func parseRules(file string) ([]*Rule, error) {
+ f, err := os.Open(file)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ var rules []*Rule
+ if err := json.NewDecoder(f).Decode(&rules); err != nil {
+ return nil, err
+ }
+ for _, r := range rules {
+ r.handler = makeHandler(r)
+ if r.handler == nil {
+ Logger.Printf("Bad rule: %#v", r)
+ }
+ }
+ return rules, nil
+}
+
+// makeHandler constructs the appropriate Handler for the given Rule.
+func makeHandler(r *Rule) http.Handler {
+ if h := r.Forward; h != "" {
+ return &httputil.ReverseProxy{
+ Director: func(req *http.Request) {
+ req.URL.Scheme = "https"
+ req.URL.Host = h
+ // req.URL.Path = "/boo1" // TODO JvD - regex to change path here
+ },
+ }
+ }
+ return nil
+}