mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Add basic authentication without dependencies
This adds a rudimentary basic auth scheme without including dependencies.
This commit is contained in:
@@ -532,9 +532,8 @@ func startRpc(server *utils.Server, internalRaterChan,
|
||||
go server.ServeGOB(cfg.RPCGOBListen)
|
||||
go server.ServeHTTP(
|
||||
cfg.HTTPListen,
|
||||
cfg.HTTPApiUseBasicAuth,
|
||||
cfg.HTTPApiBasicAuthRealm,
|
||||
cfg.HTTPApiHtpasswdFile,
|
||||
cfg.HTTPUseBasicAuth,
|
||||
cfg.HTTPAuthUsers,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -200,25 +200,24 @@ type CGRConfig struct {
|
||||
StorDBCDRSIndexes []string
|
||||
DBDataEncoding string // The encoding used to store object data in strings: <msgpack|json>
|
||||
CacheConfig *CacheConfig
|
||||
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
|
||||
DefaultTimezone string // default timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB>
|
||||
Reconnects int // number of recconect attempts in case of connection lost <-1 for infinite | nb>
|
||||
ConnectTimeout time.Duration // timeout for RPC connection attempts
|
||||
ReplyTimeout time.Duration // timeout replies if not reaching back
|
||||
ConnectAttempts int // number of initial connection attempts before giving up
|
||||
ResponseCacheTTL time.Duration // the life span of a cached response
|
||||
InternalTtl time.Duration // maximum duration to wait for internal connections before giving up
|
||||
RoundingDecimals int // Number of decimals to round end prices at
|
||||
HttpSkipTlsVerify bool // If enabled Http Client will accept any TLS certificate
|
||||
TpExportPath string // Path towards export folder for offline Tariff Plans
|
||||
RPCJSONListen string // RPC JSON listening address
|
||||
RPCGOBListen string // RPC GOB listening address
|
||||
HTTPListen string // HTTP listening address
|
||||
HTTPUseBasicAuth bool // Use basic auth for HTTP API
|
||||
HTTPAuthUsers map[string]string // Basic auth user:password map (base64 passwords)
|
||||
DefaultReqType string // Use this request type if not defined on top
|
||||
DefaultCategory string // set default type of record
|
||||
DefaultTenant string // set default tenant
|
||||
DefaultTimezone string // default timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB>
|
||||
Reconnects int // number of recconect attempts in case of connection lost <-1 for infinite | nb>
|
||||
ConnectTimeout time.Duration // timeout for RPC connection attempts
|
||||
ReplyTimeout time.Duration // timeout replies if not reaching back
|
||||
ConnectAttempts int // number of initial connection attempts before giving up
|
||||
ResponseCacheTTL time.Duration // the life span of a cached response
|
||||
InternalTtl time.Duration // maximum duration to wait for internal connections before giving up
|
||||
RoundingDecimals int // Number of decimals to round end prices at
|
||||
HttpSkipTlsVerify bool // If enabled Http Client will accept any TLS certificate
|
||||
TpExportPath string // Path towards export folder for offline Tariff Plans
|
||||
HttpPosterAttempts int
|
||||
HttpFailedDir string // Directory path where we store failed http requests
|
||||
MaxCallDuration time.Duration // The maximum call duration (used by responder when querying DerivedCharging) // ToDo: export it in configuration file
|
||||
@@ -506,7 +505,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error {
|
||||
return err
|
||||
}
|
||||
|
||||
jsnHttpApiCfg, err := jsnCfg.HttpApiJsonCfg()
|
||||
jsnHttpCfg, err := jsnCfg.HttpJsonCfg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -787,15 +786,12 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error {
|
||||
}
|
||||
}
|
||||
|
||||
if jsnHttpApiCfg != nil {
|
||||
if jsnHttpApiCfg.Use_basic_auth != nil {
|
||||
self.HTTPApiUseBasicAuth = *jsnHttpApiCfg.Use_basic_auth
|
||||
if jsnHttpCfg != nil {
|
||||
if jsnHttpCfg.Use_basic_auth != nil {
|
||||
self.HTTPUseBasicAuth = *jsnHttpCfg.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 jsnHttpCfg.Auth_users != nil {
|
||||
self.HTTPAuthUsers = *jsnHttpCfg.Auth_users
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,10 +72,9 @@ const CGRATES_CFG_JSON = `
|
||||
},
|
||||
|
||||
|
||||
"http_api": { // HTTP API configuration
|
||||
"http": { // HTTP API configuration
|
||||
"use_basic_auth": false, // use basic authentication
|
||||
"basic_auth_realm": "", // basic auth realm URL
|
||||
"htpasswd_file": "" // basic auth htpasswd file location
|
||||
"auth_users": {} // basic authentication usernames and base64-encoded passwords (eg: { "username1": "cGFzc3dvcmQ=", "username2": "cGFzc3dvcmQy "})
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ const (
|
||||
GENERAL_JSN = "general"
|
||||
CACHE_JSN = "cache"
|
||||
LISTEN_JSN = "listen"
|
||||
HTTP_API_JSN = "http_api"
|
||||
HTTP_JSN = "http"
|
||||
TPDB_JSN = "tariffplan_db"
|
||||
DATADB_JSN = "data_db"
|
||||
STORDB_JSN = "stor_db"
|
||||
@@ -119,12 +119,12 @@ func (self CgrJsonCfg) ListenJsonCfg() (*ListenJsonCfg, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (self CgrJsonCfg) HttpApiJsonCfg() (*HTTPApiJsonCfg, error) {
|
||||
rawCfg, hasKey := self[HTTP_API_JSN]
|
||||
func (self CgrJsonCfg) HttpJsonCfg() (*HTTPJsonCfg, error) {
|
||||
rawCfg, hasKey := self[HTTP_JSN]
|
||||
if !hasKey {
|
||||
return nil, nil
|
||||
}
|
||||
cfg := new(HTTPApiJsonCfg)
|
||||
cfg := new(HTTPJsonCfg)
|
||||
if err := json.Unmarshal(*rawCfg, cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -46,11 +46,10 @@ type ListenJsonCfg struct {
|
||||
Http *string
|
||||
}
|
||||
|
||||
// HTTP API config section
|
||||
type HTTPApiJsonCfg struct {
|
||||
Use_basic_auth *bool
|
||||
Basic_auth_realm *string
|
||||
Htpasswd_file *string
|
||||
// HTTP config section
|
||||
type HTTPJsonCfg struct {
|
||||
Use_basic_auth *bool
|
||||
Auth_users *map[string]string
|
||||
}
|
||||
|
||||
// Database config
|
||||
|
||||
@@ -51,12 +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
|
||||
// "http": { // HTTP API configuration
|
||||
// "use_basic_auth": false, // use basic authentication
|
||||
// "auth_users": {} // basic authentication usernames and base64-encoded passwords (eg: { "username1": "cGFzc3dvcmQ=", "username2": "cGFzc3dvcmQy "})
|
||||
// },
|
||||
|
||||
|
||||
// "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
|
||||
|
||||
19
glide.lock
generated
19
glide.lock
generated
@@ -1,8 +1,6 @@
|
||||
hash: cfda8a78a96bf7b3b471463d5ddf3330d6ae2089d5ac7c9a31b0f312e6518595
|
||||
updated: 2016-11-10T08:26:28.162167756-07:00
|
||||
hash: da953ea34fabe4e21f4dde6344f3b7ab5a75e02122a1c17af5ea434058fc77fb
|
||||
updated: 2016-09-06T20:33:01.649869367+02:00
|
||||
imports:
|
||||
- name: github.com/abbot/go-http-auth
|
||||
version: efc9484eee77263a11f158ef4f30fcc30298a942
|
||||
- name: github.com/bit4bit/gami
|
||||
version: 3a7f98e7efce7ed7f22c2169b666910b8abb15dc
|
||||
- name: github.com/cenk/hub
|
||||
@@ -28,17 +26,13 @@ imports:
|
||||
- internal/parser/findutil
|
||||
- internal/parser/intfns
|
||||
- internal/parser/pathexpr
|
||||
- literals/boollit
|
||||
- literals/numlit
|
||||
- literals/strlit
|
||||
- internal/xconst
|
||||
- internal/xsort
|
||||
- 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
|
||||
@@ -82,11 +76,6 @@ imports:
|
||||
version: 5cd0f2b3b6cca8e3a0a4101821e41a73cb59bed6
|
||||
subpackages:
|
||||
- codec
|
||||
- name: golang.org/x/crypto
|
||||
version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd
|
||||
subpackages:
|
||||
- bcrypt
|
||||
- blowfish
|
||||
- name: golang.org/x/net
|
||||
version: 1358eff22f0dd0c54fc521042cc607f6ff4b531a
|
||||
subpackages:
|
||||
|
||||
@@ -39,5 +39,3 @@ 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
|
||||
|
||||
98
utils/basic_auth.go
Normal file
98
utils/basic_auth.go
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// use provides a cleaner interface for chaining middleware for single routes.
|
||||
// Middleware functions are simple HTTP handlers (w http.ResponseWriter, r *http.Request)
|
||||
//
|
||||
// r.HandleFunc("/login", use(loginHandler, rateLimit, csrf))
|
||||
// r.HandleFunc("/form", use(formHandler, csrf))
|
||||
// r.HandleFunc("/about", aboutHandler)
|
||||
//
|
||||
// From https://gist.github.com/elithrar/9146306
|
||||
// See https://gist.github.com/elithrar/7600878#comment-955958 for how to extend it to suit simple http.Handler's
|
||||
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
|
||||
for _, m := range middleware {
|
||||
h = m(h)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
type basicAuthMiddleware func(h http.HandlerFunc) http.HandlerFunc
|
||||
|
||||
// basicAuth returns a middleware function to intercept the request and validate
|
||||
func basicAuth(userList map[string]string) basicAuthMiddleware {
|
||||
return func(h http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
|
||||
authHeader := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||
if len(authHeader) != 2 {
|
||||
Logger.Warning("<BasicAuth> Missing authorization header value")
|
||||
http.Error(w, "Not authorized", 401)
|
||||
return
|
||||
}
|
||||
|
||||
authHeaderDecoded, err := base64.StdEncoding.DecodeString(authHeader[1])
|
||||
if err != nil {
|
||||
Logger.Warning("<BasicAuth> Unable to decode authorization header")
|
||||
http.Error(w, err.Error(), 401)
|
||||
return
|
||||
}
|
||||
|
||||
userPass := strings.SplitN(string(authHeaderDecoded), ":", 2)
|
||||
if len(userPass) != 2 {
|
||||
Logger.Warning("<BasicAuth> Unauthorized API access. Missing or extra credential components")
|
||||
http.Error(w, "Not authorized", 401)
|
||||
return
|
||||
}
|
||||
|
||||
valid := verifyCredential(userPass[0], userPass[1], userList)
|
||||
if !valid {
|
||||
Logger.Warning(fmt.Sprintf("<BasicAuth> Unauthorized API access by user '%s'", userPass[0]))
|
||||
http.Error(w, "Not authorized", 401)
|
||||
return
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verifyCredential validates the incoming username and password against the authorized user list
|
||||
func verifyCredential(username string, password string, userList map[string]string) bool {
|
||||
hash, ok := userList[username]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
storedPass, err := base64.StdEncoding.DecodeString(hash)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return string(storedPass[:]) == password
|
||||
}
|
||||
47
utils/basic_auth_test.go
Normal file
47
utils/basic_auth_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestVerifyCredential(t *testing.T) {
|
||||
var hashedPasswords = map[string]string{
|
||||
"1234": "MTIzNA==",
|
||||
"bar": "YmFy",
|
||||
}
|
||||
|
||||
var verifyCredentialTests = []struct {
|
||||
username string
|
||||
password string
|
||||
userList map[string]string
|
||||
result bool
|
||||
}{
|
||||
{"test", "1234", map[string]string{"test": hashedPasswords["1234"]}, true},
|
||||
{"test", "0000", map[string]string{"test": hashedPasswords["1234"]}, false},
|
||||
{"foo", "bar", map[string]string{"test": "1234", "foo": hashedPasswords["bar"]}, true},
|
||||
{"foo", "1234", map[string]string{"test": "1234", "foo": hashedPasswords["bar"]}, false},
|
||||
{"none", "1234", map[string]string{"test": "1234", "foo": hashedPasswords["bar"]}, false},
|
||||
}
|
||||
|
||||
for _, tt := range verifyCredentialTests {
|
||||
r := verifyCredential(tt.username, tt.password, tt.userList)
|
||||
if r != tt.result {
|
||||
t.Errorf("verifyCredential(%s, %s, %v) => %t, want %t", tt.username, tt.password, tt.userList, r, tt.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/abbot/go-http-auth"
|
||||
"github.com/cenk/rpc2"
|
||||
)
|
||||
import _ "net/http/pprof"
|
||||
@@ -147,13 +146,11 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
io.Copy(w, res)
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(addr string, useBasicAuth bool, basicAuthRealm string, htpasswdFile string) {
|
||||
func (s *Server) ServeHTTP(addr string, useBasicAuth bool, userList map[string]string) {
|
||||
if s.rpcEnabled {
|
||||
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))
|
||||
Logger.Info("Configuring CGRateS HTTP server to use basic auth")
|
||||
http.HandleFunc("/jsonrpc", use(handleRequest, basicAuth(userList)))
|
||||
} else {
|
||||
http.HandleFunc("/jsonrpc", handleRequest)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user