Skip to content
This repository has been archived by the owner on Nov 8, 2022. It is now read-only.

Commit

Permalink
added global config, tests, docs for cors
Browse files Browse the repository at this point in the history
  • Loading branch information
candysmurf committed Feb 8, 2017
1 parent 73f756f commit 4abab8f
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 13 deletions.
3 changes: 3 additions & 0 deletions docs/SNAPTELD_CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ restapi:

# port sets the port to start the REST API server on. Default is 8181
port: 8181

# corsd sets the allowed origins in a comma separated list. It defaults to the same origin if the value is empty.
corsd: http://127.0.0.1:8080, http://snap.example.io, http://example.com
```
### snapteld tribe configurations
Expand Down
3 changes: 2 additions & 1 deletion examples/configs/snap-config-sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@
"rest_certificate":"/path/to/cert/file",
"rest_key":"/path/to/private/key",
"port":8282,
"addr":"127.0.0.1:12345"
"addr":"127.0.0.1:12345",
"corsd": "http://127.0.0.1:8888"
},
"tribe":{
"enable":true,
Expand Down
3 changes: 3 additions & 0 deletions examples/configs/snap-config-sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,6 @@ tribe:

# seed sets the snapteld instance to use as the seed for tribe communications
seed: 1.1.1.1:16000

# corsd sets the cors allowed domains in a comma separated list. It is the same origin if it's empty.
corsd: 127.0.0.1:88888
6 changes: 6 additions & 0 deletions mgmt/rest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
defaultAuthPassword string = ""
defaultPortSetByConfig bool = false
defaultPprof bool = false
defaultCorsd string = ""
)

// holds the configuration passed in through the SNAP config file
Expand All @@ -29,6 +30,7 @@ type Config struct {
RestAuthPassword string `json:"rest_auth_password"yaml:"rest_auth_password"`
portSetByConfig bool ``
Pprof bool `json:"pprof"yaml:"pprof"`
Corsd string `json:"corsd"yaml:"corsd"`
}

const (
Expand Down Expand Up @@ -64,6 +66,9 @@ const (
},
"pprof": {
"type": "boolean"
},
"corsd" : {
"type": "string"
}
},
"additionalProperties": false
Expand All @@ -84,6 +89,7 @@ func GetDefaultConfig() *Config {
RestAuthPassword: defaultAuthPassword,
portSetByConfig: defaultPortSetByConfig,
Pprof: defaultPprof,
Corsd: defaultCorsd,
}
}

Expand Down
6 changes: 5 additions & 1 deletion mgmt/rest/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ var (
Name: "pprof",
Usage: "Enables profiling tools",
}
flCorsd = cli.StringFlag{
Name: "corsd",
Usage: "Define Cors Domains",
}

// Flags consumed by snapteld
Flags = []cli.Flag{flAPIDisabled, flAPIAddr, flAPIPort, flRestHTTPS, flRestCert, flRestKey, flRestAuth, flPProf}
Flags = []cli.Flag{flAPIDisabled, flAPIAddr, flAPIPort, flRestHTTPS, flRestCert, flRestKey, flRestAuth, flPProf, flCorsd}
)
64 changes: 53 additions & 11 deletions mgmt/rest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"fmt"
"net"
"net/http"
"strconv"
"sync"
"time"

Expand All @@ -33,11 +34,19 @@ import (
"github.com/rs/cors"
"github.com/urfave/negroni"

"strings"

"github.com/intelsdi-x/snap/mgmt/rest/api"
"github.com/intelsdi-x/snap/mgmt/rest/v1"
"github.com/intelsdi-x/snap/mgmt/rest/v2"
)

const (
allowedMethods = "GET, POST, DELETE, PUT, OPTIONS"
allowedHeaders = "Origin, X-Requested-With, Content-Type, Accept"
maxAge = 3600
)

var (
ErrBadCert = errors.New("Invalid certificate given")

Expand All @@ -61,6 +70,7 @@ type Server struct {
// the following instance variables are used to cleanly shutdown the server
serverListener net.Listener
closingChan chan bool
allowedOrigins map[string]bool
}

// New creates a REST API server with a given config
Expand Down Expand Up @@ -94,13 +104,18 @@ func New(cfg *Config) (*Server, error) {
)
s.r = httprouter.New()

// Deals with CORS
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET, POST, DELETE, PUT, PATCH, OPTIONS"},
AllowedHeaders: []string{"Origin, X-Requested-With, Content-Type, Accept"},
})
s.n.Use(c)
// CORS has to be turned on explictly in the global config.
// Otherwise, it defauts to the same origin.
origins := s.getAllowedOrigins(cfg.Corsd)
if len(origins) > 0 {
c := cors.New(cors.Options{
AllowedOrigins: origins,
AllowedMethods: []string{allowedMethods},
AllowedHeaders: []string{allowedHeaders},
MaxAge: maxAge,
})
s.n.Use(c)
}

// Use negroni to handle routes
s.n.UseHandler(s.r)
Expand Down Expand Up @@ -143,10 +158,8 @@ func (s *Server) SetAPIAuthPwd(pwd string) {

// Auth Middleware for REST API
func (s *Server) authMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
// Deals with CORS
rw.Header().Set("Access-Control-Allow-Origin", "*")
rw.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, OPTIONS")
rw.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
reqOrigin := r.Header.Get("Origin")
s.setAllowedOrigins(rw, reqOrigin)

defer r.Body.Close()
if s.auth {
Expand All @@ -164,6 +177,23 @@ func (s *Server) authMiddleware(rw http.ResponseWriter, r *http.Request, next ht
}
}

// CORS origins have to be turned on explictly in the global config.
// Otherwise, it defaults to the same origin.
func (s *Server) setAllowedOrigins(rw http.ResponseWriter, ro string) {
if len(s.allowedOrigins) > 0 {
if _, ok := s.allowedOrigins[ro]; ok {
// localhost CORS is not supported by all browsers. It has to use "*".
if strings.Contains(ro, "127.0.0.1") || strings.Contains(ro, "localhost") {
ro = "*"
}
rw.Header().Set("Access-Control-Allow-Origin", ro)
rw.Header().Set("Access-Control-Allow-Methods", allowedMethods)
rw.Header().Set("Access-Control-Allow-Headers", allowedHeaders)
rw.Header().Set("Access-Control-Max-Age", strconv.Itoa(maxAge))
}
}
}

func (s *Server) SetAddress(addrString string) {
s.addrString = addrString
restLogger.Info(fmt.Sprintf("Address used for binding: [%v]", s.addrString))
Expand Down Expand Up @@ -274,6 +304,18 @@ func (s *Server) addRoutes() {
s.addPprofRoutes()
}

func (s *Server) getAllowedOrigins(corsd string) []string {
if corsd == "" {
return []string{}
}
os := strings.Split(corsd, ",")
s.allowedOrigins = map[string]bool{}
for _, o := range os {
s.allowedOrigins[strings.TrimSpace(o)] = true
}
return os
}

// Monkey patch ListenAndServe and TCP alive code from https://golang.org/src/net/http/server.go
// The built in ListenAndServe and ListenAndServeTLS include TCP keepalive
// At this point the Go team is not wanting to provide separate listen and serve methods
Expand Down
42 changes: 42 additions & 0 deletions mgmt/rest/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ limitations under the License.
package rest

import (
"strings"
"testing"

"github.com/intelsdi-x/snap/pkg/cfgfile"
. "github.com/smartystreets/goconvey/convey"
"github.com/urfave/negroni"
)

const (
Expand Down Expand Up @@ -161,5 +163,45 @@ func TestRestAPIDefaultConfig(t *testing.T) {
Convey("RestKey should be empty", func() {
So(cfg.RestKey, ShouldEqual, "")
})
Convey("Corsd should be empty", func() {
So(cfg.Corsd, ShouldEqual, "")
})
})
}

type mockServer struct {
n *negroni.Negroni
allowedOrigins map[string]bool
}

func NewMockServer(cfg *Config) (*mockServer, []string) {
s := &mockServer{}
origins := s.getAllowedOrigins(cfg.Corsd)
return s, origins
}

func (s *mockServer) getAllowedOrigins(corsd string) []string {
if corsd == "" {
return []string{}
}
os := strings.Split(corsd, ",")
s.allowedOrigins = map[string]bool{}
for _, o := range os {
s.allowedOrigins[strings.TrimSpace(o)] = true
}
return os
}

func TestRestAPICorsd(t *testing.T) {
cfg := GetDefaultConfig()

Convey("Test cors origin list", t, func() {
cfg.Corsd = "http://127.0.0.1:80, http://example.com"
s, o := NewMockServer(cfg)

Convey("Origins match results", func() {
So(s.allowedOrigins, ShouldResemble, map[string]bool{"http://127.0.0.1:80": true, "http://example.com": true})
So(len(o), ShouldEqual, 2)
})
})
}
2 changes: 2 additions & 0 deletions snapteld.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,8 @@ func applyCmdLineFlags(cfg *Config, ctx *cli.Context) {
cfg.RestAPI.RestAuth = setBoolVal(cfg.RestAPI.RestAuth, ctx, "rest-auth")
cfg.RestAPI.RestAuthPassword = setStringVal(cfg.RestAPI.RestAuthPassword, ctx, "rest-auth-pwd")
cfg.RestAPI.Pprof = setBoolVal(cfg.RestAPI.Pprof, ctx, "pprof")
cfg.RestAPI.Corsd = setStringVal(cfg.RestAPI.Corsd, ctx, "corsd")

// next for the scheduler related flags
cfg.Scheduler.WorkManagerQueueSize = setUIntVal(cfg.Scheduler.WorkManagerQueueSize, ctx, "work-manager-queue-size")
cfg.Scheduler.WorkManagerPoolSize = setUIntVal(cfg.Scheduler.WorkManagerPoolSize, ctx, "work-manager-pool-size")
Expand Down

0 comments on commit 4abab8f

Please sign in to comment.