Skip to content

Commit

Permalink
BasicAuth initial implementation (untested)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgrisham committed Sep 7, 2017
1 parent b911aa7 commit b010c40
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 24 deletions.
66 changes: 46 additions & 20 deletions api/restapi/restapi.go
Expand Up @@ -56,6 +56,11 @@ type RESTAPI struct {
wg sync.WaitGroup
}

type Config struct {
TLS *tls.Config
BasicAuthCreds map[string]string
}

type route struct {
Name string
Method string
Expand All @@ -70,33 +75,26 @@ type peerAddBody struct {
// NewRESTAPI creates a new REST API component. It receives
// the multiaddress on which the API listens.
func NewRESTAPI(apiMAddr ma.Multiaddr) (*RESTAPI, error) {
n, addr, err := manet.DialArgs(apiMAddr)
if err != nil {
return nil, err
}
l, err := net.Listen(n, addr)
if err != nil {
return nil, err
}
return newRESTAPI(apiMAddr, l)
return NewRESTAPIWithConfig(apiMAddr, &Config{})
}

// NewTlsRESTAPI creates a new REST API component that uses TLS for security
// (authentication, encryption). It receives the multiaddress on which the API
// listens, as well as paths to certificate and private key files
func NewTLSRESTAPI(apiMAddr ma.Multiaddr, tlsCfg *tls.Config) (*RESTAPI, error) {
// NewRESTAPI creates a new REST API component. It receives
// the multiaddress on which the API listens.
func NewRESTAPIWithConfig(apiMAddr ma.Multiaddr, cfg *Config) (*RESTAPI, error) {
n, addr, err := manet.DialArgs(apiMAddr)
if err != nil {
return nil, err
}
l, err := tls.Listen(n, addr, tlsCfg)
var l net.Listener
if cfg.TLS != nil {
l, err = tls.Listen(n, addr, cfg.TLS)
} else {
l, err = net.Listen(n, addr)
}
if err != nil {
return nil, err
}
return newRESTAPI(apiMAddr, l)
}

func newRESTAPI(apiMAddr ma.Multiaddr, l net.Listener) (*RESTAPI, error) {
router := mux.NewRouter().StrictSlash(true)
s := &http.Server{
ReadTimeout: RESTAPIServerReadTimeout,
Expand All @@ -116,18 +114,46 @@ func newRESTAPI(apiMAddr ma.Multiaddr, l net.Listener) (*RESTAPI, error) {
server: s,
rpcReady: make(chan struct{}, 1),
}
api.addRoutes(router, cfg.BasicAuthCreds)
api.run()

return api, nil
}

func (api *RESTAPI) addRoutes(router *mux.Router, basicAuthCreds map[string]string) {
for _, route := range api.routes() {
if basicAuthCreds != nil {
route.HandlerFunc = basicAuth(route.HandlerFunc, basicAuthCreds)
}
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(route.HandlerFunc)
}

api.router = router
api.run()
return api, nil
}

func basicAuth(h http.HandlerFunc, credentials map[string]string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
username, password, ok := r.BasicAuth()
if !ok {
http.Error(w, "Not authorized", 401)
return
}
authorized := false
for u, p := range credentials {
if u == username && p == password {
authorized = true
}
}
if !authorized {
http.Error(w, "Not authorized", 401)
return
}
h.ServeHTTP(w, r)
}
}

func (rest *RESTAPI) routes() []route {
Expand Down
14 changes: 14 additions & 0 deletions config.go
Expand Up @@ -74,6 +74,12 @@ type Config struct {
// SSLCertFile.
SSLKeyFile string

// BasicAuthCredentials is a map of username, password pairs used to authorize
// access to the REST API. It is *highly recommended* that you use this in
// conjunction with SSL, as the username/password sent by the client are not
// encrypted when using HTTP without SSL.
BasicAuthCredentials map[string]string

// Listen parameters for the IPFS Proxy. Used by the IPFS
// connector component.
IPFSProxyAddr ma.Multiaddr
Expand Down Expand Up @@ -158,6 +164,12 @@ type JSONConfig struct {
// SSLCertFile.
SSLKeyFile string `json:"ssl_key_file,omitempty"`

// BasicAuthCredentials is a map of username, password pairs used to authorize
// access to the REST API. It is *highly recommended* that you use this in
// conjunction with SSL, as the username/password sent by the client are not
// encrypted when using HTTP without SSL.
BasicAuthCredentials map[string]string `json:"basic_auth_credentials"`

// Listen address for the IPFS Proxy, which forwards requests to
// an IPFS daemon.
IPFSProxyListenMultiaddress string `json:"ipfs_proxy_listen_multiaddress"`
Expand Down Expand Up @@ -234,6 +246,7 @@ func (cfg *Config) ToJSONConfig() (j *JSONConfig, err error) {
APIListenMultiaddress: cfg.APIAddr.String(),
SSLCertFile: cfg.SSLCertFile,
SSLKeyFile: cfg.SSLKeyFile,
BasicAuthCredentials: cfg.BasicAuthCredentials,
IPFSProxyListenMultiaddress: cfg.IPFSProxyAddr.String(),
IPFSNodeMultiaddress: cfg.IPFSNodeAddr.String(),
ConsensusDataFolder: cfg.ConsensusDataFolder,
Expand Down Expand Up @@ -347,6 +360,7 @@ func (jcfg *JSONConfig) ToConfig() (c *Config, err error) {
APIAddr: apiAddr,
SSLCertFile: jcfg.SSLCertFile,
SSLKeyFile: jcfg.SSLKeyFile,
BasicAuthCredentials: jcfg.BasicAuthCredentials,
IPFSProxyAddr: ipfsProxyAddr,
IPFSNodeAddr: ipfsNodeAddr,
ConsensusDataFolder: jcfg.ConsensusDataFolder,
Expand Down
8 changes: 4 additions & 4 deletions ipfs-cluster-service/main.go
Expand Up @@ -275,13 +275,13 @@ func run(c *cli.Context) error {
}

var api *restapi.RESTAPI
var tlsCfg *tls.Config
if len(cfg.SSLCertFile) != 0 || len(cfg.SSLKeyFile) != 0 {
tlsCfg, err := newTLSConfig(cfg.SSLCertFile, cfg.SSLKeyFile)
tlsCfg, err = newTLSConfig(cfg.SSLCertFile, cfg.SSLKeyFile)
checkErr("creating TLS config: ", err)
api, err = restapi.NewTLSRESTAPI(cfg.APIAddr, tlsCfg)
} else {
api, err = restapi.NewRESTAPI(cfg.APIAddr)
}
apiConfig := &restapi.Config{TLS: tlsCfg, BasicAuthCreds: cfg.BasicAuthCredentials}
api, err = restapi.NewRESTAPIWithConfig(cfg.APIAddr, apiConfig)
checkErr("creating REST API component", err)

proxy, err := ipfshttp.NewConnector(
Expand Down

0 comments on commit b010c40

Please sign in to comment.