Add basic authentication to HTTP API

This adds support for basic authentication for the HTTP API with new configuration values to control if basic auth is used, the basic auth realm URL, and the location to a basic .htpasswd file.

github.com/abbot/go-http-auth is added as a dependency as it efficiently implements basic auth, as well as .htpasswd file monitoring.
This commit is contained in:
Shane Neuerburg
2016-11-10 10:57:55 -07:00
parent 9784dc7c38
commit a88a1e75ed
9 changed files with 96 additions and 19 deletions

View File

@@ -530,7 +530,12 @@ func startRpc(server *utils.Server, internalRaterChan,
}
go server.ServeJSON(cfg.RPCJSONListen)
go server.ServeGOB(cfg.RPCGOBListen)
go server.ServeHTTP(cfg.HTTPListen)
go server.ServeHTTP(
cfg.HTTPListen,
cfg.HTTPApiUseBasicAuth,
cfg.HTTPApiBasicAuthRealm,
cfg.HTTPApiHtpasswdFile,
)
}
func writePid() {

View File

@@ -203,6 +203,9 @@ type CGRConfig struct {
RPCJSONListen string // RPC JSON listening address
RPCGOBListen string // RPC GOB listening address
HTTPListen string // HTTP listening address
HTTPApiUseBasicAuth bool // Use basic auth for HTTP API
HTTPApiBasicAuthRealm string // Basic auth realm URL
HTTPApiHtpasswdFile string // Basic auth htpasswd file path
DefaultReqType string // Use this request type if not defined on top
DefaultCategory string // set default type of record
DefaultTenant string // set default tenant
@@ -503,6 +506,11 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error {
return err
}
jsnHttpApiCfg, err := jsnCfg.HttpApiJsonCfg()
if err != nil {
return err
}
jsnTpDbCfg, err := jsnCfg.DbJsonCfg(TPDB_JSN)
if err != nil {
return err
@@ -779,6 +787,18 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error {
}
}
if jsnHttpApiCfg != nil {
if jsnHttpApiCfg.Use_basic_auth != nil {
self.HTTPApiUseBasicAuth = *jsnHttpApiCfg.Use_basic_auth
}
if jsnHttpApiCfg.Basic_auth_realm != nil {
self.HTTPApiBasicAuthRealm = *jsnHttpApiCfg.Basic_auth_realm
}
if jsnHttpApiCfg.Htpasswd_file != nil {
self.HTTPApiHtpasswdFile = *jsnHttpApiCfg.Htpasswd_file
}
}
if jsnRALsCfg != nil {
if jsnRALsCfg.Enabled != nil {
self.RALsEnabled = *jsnRALsCfg.Enabled

View File

@@ -45,7 +45,7 @@ const CGRATES_CFG_JSON = `
"internal_ttl": "2m", // maximum duration to wait for internal connections before giving up
"locking_timeout": "5s", // timeout internal locks to avoid deadlocks
"cache_dump_dir": "", // cache dump for faster start (leave empty to disable)
"log_level": 6, // control the level of messages logged (0-emerg to 7-debug)
"log_level": 6, // control the level of messages logged (0-emerg to 7-debug)
},
@@ -72,6 +72,13 @@ const CGRATES_CFG_JSON = `
},
"http_api": { // HTTP API configuration
"use_basic_auth": false, // use basic authentication
"basic_auth_realm": "", // basic auth realm URL
"htpasswd_file": "" // basic auth htpasswd file location
},
"tariffplan_db": { // database used to store active tariff plan configuration
"db_type": "redis", // tariffplan_db type: <redis|mongo>
"db_host": "127.0.0.1", // tariffplan_db host address
@@ -244,7 +251,7 @@ const CGRATES_CFG_JSON = `
"sms_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from SMS unit to call duration in some billing systems)
"mms_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from MMS unit to call duration in some billing systems)
"generic_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from GENERIC unit to call duration in some billing systems)
"cost_multiply_factor": 1, // multiply cost before export, eg: add VAT
"cost_multiply_factor": 1, // multiply cost before export, eg: add VAT
"export_directory": "/var/spool/cgrates/cdre", // path where the exported CDRs will be placed
"header_fields": [], // template of the exported header fields
"content_fields": [ // template of the exported content fields

View File

@@ -29,6 +29,7 @@ const (
GENERAL_JSN = "general"
CACHE_JSN = "cache"
LISTEN_JSN = "listen"
HTTP_API_JSN = "http_api"
TPDB_JSN = "tariffplan_db"
DATADB_JSN = "data_db"
STORDB_JSN = "stor_db"
@@ -118,6 +119,18 @@ func (self CgrJsonCfg) ListenJsonCfg() (*ListenJsonCfg, error) {
return cfg, nil
}
func (self CgrJsonCfg) HttpApiJsonCfg() (*HTTPApiJsonCfg, error) {
rawCfg, hasKey := self[HTTP_API_JSN]
if !hasKey {
return nil, nil
}
cfg := new(HTTPApiJsonCfg)
if err := json.Unmarshal(*rawCfg, cfg); err != nil {
return nil, err
}
return cfg, nil
}
func (self CgrJsonCfg) DbJsonCfg(section string) (*DbJsonCfg, error) {
rawCfg, hasKey := self[section]
if !hasKey {

View File

@@ -46,6 +46,13 @@ type ListenJsonCfg struct {
Http *string
}
// HTTP API config section
type HTTPApiJsonCfg struct {
Use_basic_auth *bool
Basic_auth_realm *string
Htpasswd_file *string
}
// Database config
type DbJsonCfg struct {
Db_type *string

View File

@@ -51,6 +51,12 @@
// },
// "http_api" { // HTTP API configuration
// "use_basic_auth": false, // use basic authentication
// "basic_auth_realm": "", // basic auth realm URL
// "htpasswd_file": "", // basic auth htpasswd file location
// },
// "tariffplan_db": { // database used to store active tariff plan configuration
// "db_type": "redis", // tariffplan_db type: <redis|mongo>
// "db_host": "127.0.0.1", // tariffplan_db host address
@@ -199,7 +205,7 @@
// "sms_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from SMS unit to call duration in some billing systems)
// "mms_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from MMS unit to call duration in some billing systems)
// "generic_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from GENERIC unit to call duration in some billing systems)
// "cost_multiply_factor": 1, // multiply cost before export, eg: add VAT
// "cost_multiply_factor": 1, // multiply cost before export, eg: add VAT
// "export_directory": "/var/spool/cgrates/cdre", // path where the exported CDRs will be placed
// "header_fields": [], // template of the exported header fields
// "content_fields": [ // template of the exported content fields

19
glide.lock generated
View File

@@ -1,6 +1,8 @@
hash: da953ea34fabe4e21f4dde6344f3b7ab5a75e02122a1c17af5ea434058fc77fb
updated: 2016-09-06T20:33:01.649869367+02:00
hash: cfda8a78a96bf7b3b471463d5ddf3330d6ae2089d5ac7c9a31b0f312e6518595
updated: 2016-11-10T08:26:28.162167756-07:00
imports:
- name: github.com/abbot/go-http-auth
version: efc9484eee77263a11f158ef4f30fcc30298a942
- name: github.com/bit4bit/gami
version: 3a7f98e7efce7ed7f22c2169b666910b8abb15dc
- name: github.com/cenk/hub
@@ -26,13 +28,17 @@ imports:
- internal/parser/findutil
- internal/parser/intfns
- internal/parser/pathexpr
- internal/xconst
- internal/xsort
- literals/boollit
- literals/numlit
- literals/strlit
- tree
- tree/xmltree
- tree/xmltree/xmlbuilder
- tree/xmltree/xmlele
- tree/xmltree/xmlnode
- xconst
- xfn
- xsort
- name: github.com/DisposaBoy/JsonConfigReader
version: 33a99fdf1d5ee1f79b5077e9c06f955ad356d5f4
- name: github.com/fiorix/go-diameter
@@ -76,6 +82,11 @@ imports:
version: 5cd0f2b3b6cca8e3a0a4101821e41a73cb59bed6
subpackages:
- codec
- name: golang.org/x/crypto
version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd
subpackages:
- bcrypt
- blowfish
- name: golang.org/x/net
version: 1358eff22f0dd0c54fc521042cc607f6ff4b531a
subpackages:

View File

@@ -39,3 +39,5 @@ import:
- package: github.com/hashicorp/golang-lru
- package: github.com/cgrates/aringo
- package: github.com/bit4bit/gami
- package: github.com/abbot/go-http-auth
version: ~0.3.0

View File

@@ -29,8 +29,8 @@ import (
"reflect"
"time"
"github.com/abbot/go-http-auth"
"github.com/cenk/rpc2"
"golang.org/x/net/websocket"
)
import _ "net/http/pprof"
@@ -140,17 +140,23 @@ func (s *Server) ServeGOB(addr string) {
}
}
func (s *Server) ServeHTTP(addr string) {
func handleRequest(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
w.Header().Set("Content-Type", "application/json")
res := NewRPCRequest(r.Body).Call()
io.Copy(w, res)
}
func (s *Server) ServeHTTP(addr string, useBasicAuth bool, basicAuthRealm string, htpasswdFile string) {
if s.rpcEnabled {
http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
w.Header().Set("Content-Type", "application/json")
res := NewRPCRequest(req.Body).Call()
io.Copy(w, res)
})
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
jsonrpc.ServeConn(ws)
}))
if useBasicAuth {
Logger.Info(fmt.Sprintf("Configuring CGRateS HTTP server to use basic auth (realm: %s, htpasswd: %s).", basicAuthRealm, htpasswdFile))
secrets := auth.HtpasswdFileProvider(htpasswdFile)
authenticator := auth.NewBasicAuthenticator(basicAuthRealm, secrets)
http.HandleFunc("/jsonrpc", auth.JustCheck(authenticator, handleRequest))
} else {
http.HandleFunc("/jsonrpc", handleRequest)
}
s.httpEnabled = true
}
if !s.httpEnabled {