Detect if a user is Admin and do not send AuthUser

This is done by parsing users.mk with python, the users.mk will be watched by inotify for changes.

Signed-off-by: René Jochum <rene@webmeisterei.com>
master
René Jochum 4 years ago
parent f1300b5c5f
commit d62c1382ca

@ -2,7 +2,7 @@
LQL API Client/Server for check_mk
See [the LQL Docs](https://checkmk.com/cms_livestatus.html) for what LQL can do for you.
Look at [the LQL Docs](https://checkmk.com/cms_livestatus.html) to see what LQL can do for you.
## Commands the client supports

@ -20,11 +20,12 @@ func init() {
localClientCmdLimit := 0
localClientCmd.Flags().StringP("socket", "s", "/opt/omd/sites/{site}/tmp/run/live", "Socket on the Server")
localClientCmd.Flags().StringP("liveproxydir", "p", "/opt/omd/sites/{site}/tmp/run/liveproxy", "Directory which contains liveproxy sockets")
localClientCmd.Flags().StringP("multsiteusers", "m", "/opt/omd/sites/{site}/etc/check_mk/multisite.d/wato/users.mk", "Your checkmks users.mk file")
localClientCmd.Flags().BoolP("debug", "d", false, "Enable Debug on stderr")
localClientCmd.Flags().StringP("format", "f", "jsonparsed", "Format one of: python, python3, json, csv, CSV, jsonparsed (default is jsonparsed, I parse json from the server)")
localClientCmd.Flags().StringP("table", "t", "", "Produce a GET request for the given table (default: supply request by stdin)")
localClientCmd.Flags().StringArrayP("columns", "c", []string{""}, "Columns to show from the given table, this is required if you give a table!")
localClientCmd.Flags().StringP("user", "u", "", "LQL user to limit this on")
localClientCmd.Flags().StringP("user", "u", "", "CheckMK user to limit this request on")
localClientCmd.Flags().IntVarP(&localClientCmdLimit, "limit", "l", 0, "Limit request lines")
rootCmd.AddCommand(localClientCmd)
}
@ -52,6 +53,7 @@ Examples:
sReplacer := strings.NewReplacer("{site}", args[0])
destSocket := sReplacer.Replace(cmd.Flag("socket").Value.String())
liveproxyDir := sReplacer.Replace(cmd.Flag("liveproxydir").Value.String())
multisiteUsersFile := sReplacer.Replace(cmd.Flag("multsiteusers").Value.String())
var lqlClient lql.Client
logger := log.New()
@ -107,7 +109,7 @@ Examples:
os.Exit(1)
}(sigc)
lqlClient, err := lql.NewMultiClient(1, 1, destSocket, liveproxyDir)
lqlClient, err := lql.NewMultiClient(1, 1, destSocket, liveproxyDir, multisiteUsersFile)
if err != nil {
logger.WithField("error", err).Error()
return

@ -21,6 +21,7 @@ func init() {
localServerCmd.Flags().StringP("socket", "s", "/opt/omd/sites/{site}/tmp/run/live", "Socket")
localServerCmd.Flags().StringP("liveproxydir", "p", "/opt/omd/sites/{site}/tmp/run/liveproxy", "Directory which contains liveproxy sockets")
localServerCmd.Flags().StringP("multsiteusers", "u", "/opt/omd/sites/{site}/etc/check_mk/multisite.d/wato/users.mk", "Your checkmks users.mk file")
localServerCmd.Flags().StringP("htpasswd", "t", "/opt/omd/sites/{site}/etc/htpasswd", "htpasswd file")
localServerCmd.Flags().BoolP("debug", "d", false, "Enable Debug on stderr")
localServerCmd.Flags().StringP("listen", "l", ":8080", "Address to listen on")
@ -38,6 +39,7 @@ Requires a local lql unix socket.`,
Run: func(cmd *cobra.Command, args []string) {
sReplacer := strings.NewReplacer("{site}", args[0])
liveproxyDir := sReplacer.Replace(cmd.Flag("liveproxydir").Value.String())
multisiteUsersFile := sReplacer.Replace(cmd.Flag("multsiteusers").Value.String())
logfile, err := cmd.Flags().GetString("logfile")
if err != nil {
@ -97,7 +99,7 @@ Requires a local lql unix socket.`,
return
}
lqlClient, err = lql.NewMultiClient(minConns, maxConns, localSocket, liveproxyDir)
lqlClient, err = lql.NewMultiClient(minConns, maxConns, localSocket, liveproxyDir, multisiteUsersFile)
if err != nil {
logger.WithField("error", err).Error()
return

6
debian/changelog vendored

@ -1,3 +1,9 @@
lql-api (0.0.11-alpha) UNRELEASED; urgency=medium
* No changes yet
-- René Jochum <rene@webmeisterei.com> Sun, 04 Oct 2020 21:39:06 +0200
lql-api (0.0.10-alpha) RELEASED; urgency=medium
* Implement Multisite support (multiple sockets)

@ -8,6 +8,7 @@ require (
github.com/gin-gonic/gin v1.6.3
github.com/hashicorp/go-multierror v1.1.0
github.com/loopfz/gadgeto v0.10.1
github.com/micro/go-micro/v2 v2.9.1
github.com/sirupsen/logrus v1.7.0
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.4.0
@ -16,4 +17,5 @@ require (
github.com/webmeisterei/go-http-auth v0.5.0
github.com/wolviecb/basic-auth v0.5.0
golang.org/x/crypto v0.0.0-20200930160638-afb6bcd081ae
gopkg.in/fsnotify.v1 v1.4.7
)

@ -253,6 +253,7 @@ github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
@ -416,6 +417,7 @@ github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4f
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/micro/cli/v2 v2.1.2 h1:43J1lChg/rZCC1rvdqZNFSQDrGT7qfMrtp6/ztpIkEM=
github.com/micro/cli/v2 v2.1.2/go.mod h1:EguNh6DAoWKm9nmk+k/Rg0H3lQnDxqzu5x5srOtGtYg=
github.com/micro/go-micro v1.18.0 h1:gP70EZVHpJuUIT0YWth192JmlIci+qMOEByHm83XE9E=
github.com/micro/go-micro/v2 v2.9.1 h1:+S9koIrNWARjpP6k2TZ7kt0uC9zUJtNXzIdZTZRms7Q=
github.com/micro/go-micro/v2 v2.9.1/go.mod h1:x55ZM3Puy0FyvvkR3e0ha0xsE9DFwfPSUMWAIbFY0SY=
github.com/micro/go-micro/v3 v3.0.0-beta.2.0.20200929133051-87e898f4fc62 h1:yFK8GK2Lt43YHgF7k7q8NNsgz+0vfTOdv0R/YPafFWQ=
@ -804,6 +806,7 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c h1:38q6VNPWR010vN82/SB121GujZNIfAUb4YttE2rhGuc=
@ -897,6 +900,7 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=

@ -9,6 +9,7 @@ import (
type Client interface {
ClientCount() int
IsAdmin(username string) bool
SetLogger(logger *log.Logger)
Close() error
Request(context context.Context, request, authUser string, limit int) ([]gin.H, error)

@ -17,18 +17,25 @@ type MultiClient struct {
localSocket string
liveproxyDir string
clients map[string]Client
usersWatcher *UsersWatcher
}
func NewMultiClient(minConn, maxConn int, localSocket, liveproxyDir string) (Client, error) {
func NewMultiClient(minConn, maxConn int, localSocket, liveproxyDir string, multisiteUsersFile string) (Client, error) {
uw, err := NewUsersWatcher(multisiteUsersFile)
if err != nil {
return nil, err
}
mc := &MultiClient{
minConn: minConn,
maxConn: maxConn,
localSocket: localSocket,
liveproxyDir: liveproxyDir,
clients: make(map[string]Client),
usersWatcher: uw,
}
err := mc.CreateClients()
err = mc.CreateClients()
if err != nil {
return nil, err
}
@ -72,7 +79,13 @@ func (c *MultiClient) CreateClients() error {
return result
}
func (c *MultiClient) IsAdmin(username string) bool {
return c.usersWatcher.IsAdmin(username)
}
func (c *MultiClient) SetLogger(logger *log.Logger) {
c.usersWatcher.SetLogger(logger)
for _, client := range c.clients {
client.SetLogger(logger)
}

@ -53,6 +53,10 @@ func (c *SingleClient) SetLogger(logger *log.Logger) {
c.logger = logger
}
func (c *SingleClient) IsAdmin(username string) bool {
return false
}
func (c *SingleClient) Close() error {
c.logger.WithFields(log.Fields{"address": c.address}).Debug("Closing pool")
return c.pool.Close()

@ -0,0 +1,166 @@
package lql
import (
"encoding/json"
"fmt"
"io/ioutil"
"os/exec"
"path/filepath"
"sync"
log "github.com/sirupsen/logrus"
"gopkg.in/fsnotify.v1"
)
const usersExporterFile = `from __future__ import print_function
import json
class MultiSiteUsers(object):
def update(self, data):
print(json.dumps(data));
multisite_users = MultiSiteUsers()
eval(open("%s").read())
`
type UserData struct {
ForceAuthUserWebservice bool `json:"force_authuser_webservice"`
Looked bool `json:"locked"`
Roles []string `json:"roles"`
ForceAuthUser bool `json:"force_authuser"`
Alias string `json:"alias"`
StartUrl string `json:"start_url"`
}
type UsersWatcher struct {
usersfile string
users map[string]UserData
lock *sync.RWMutex
logger *log.Logger
isWatching bool
watcher *fsnotify.Watcher
}
func NewUsersWatcher(usersfile string) (*UsersWatcher, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
uw := &UsersWatcher{
usersfile: usersfile,
lock: &sync.RWMutex{},
isWatching: false,
watcher: watcher,
}
return uw, nil
}
func (uw *UsersWatcher) Close() {
uw.watcher.Close()
}
func (uw *UsersWatcher) SetLogger(logger *log.Logger) {
uw.logger = logger
}
func (uw *UsersWatcher) StartWatching() {
go func() {
for {
select {
case event, ok := <-uw.watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write == fsnotify.Write {
uw.FetchUsers()
}
case err, ok := <-uw.watcher.Errors:
if !ok {
return
}
uw.logger.WithField("error", err).Error()
}
}
}()
uw.lock.Lock()
uw.isWatching = true
uw.lock.Unlock()
err := uw.watcher.Add(uw.usersfile)
if err != nil {
uw.logger.WithField("error", err).Error()
}
}
func (uw *UsersWatcher) IsAdmin(userName string) bool {
uw.lock.RLock()
if uw.users == nil {
uw.lock.RUnlock()
if !uw.isWatching {
uw.StartWatching()
}
uw.FetchUsers()
uw.lock.RLock()
}
defer uw.lock.RUnlock()
userData, ok := uw.users[userName]
if !ok {
uw.logger.WithField("user_name", userName).Debug("Failed to fetch user from db")
return false
}
for _, role := range userData.Roles {
if role == "admin" {
uw.logger.WithField("user_name", userName).Trace("User is admin")
return true
}
}
uw.logger.WithField("user_name", userName).Trace("User is not admin")
return false
}
func (uw *UsersWatcher) FetchUsers() error {
uw.logger.WithField("usersfile", uw.usersfile).Debug("Reading users")
dir, err := ioutil.TempDir("", "lql-api")
if err != nil {
uw.logger.WithField("error", err).Error()
return err
}
tmpfn := filepath.Join(dir, "lql-api-user-reader.py")
if err := ioutil.WriteFile(tmpfn, []byte(fmt.Sprintf(usersExporterFile, uw.usersfile)), 0700); err != nil {
uw.logger.WithField("error", err).Error()
return err
}
cmd := exec.Command("python", tmpfn)
uw.logger.WithField("args", cmd.Args).Debug("Executing")
out, err := cmd.CombinedOutput()
if err != nil {
uw.logger.WithField("error", err).Error()
return err
}
result := make(map[string]UserData, 1)
if err = json.Unmarshal(out, &result); err != nil {
uw.logger.WithField("error", err).Error()
return err
}
uw.lock.Lock()
uw.users = result
uw.lock.Unlock()
return nil
}

@ -8,6 +8,9 @@ func v1Ping(c *gin.Context) (gin.H, error) {
return nil, err
}
user := c.GetString("user")
if client.IsAdmin(user) {
user = ""
}
msg := `GET hosts
Columns: name`

@ -31,6 +31,9 @@ func v1RawPost(c *gin.Context, params *V1RawRequestParams) ([]gin.H, error) {
return nil, err
}
user := c.GetString("user")
if client.IsAdmin(user) {
user = ""
}
// Param validation and request building
request := []string{}

@ -25,6 +25,9 @@ func v1StatsGetTacticalOverview(c *gin.Context) (*V1StatsTacticalOverview, error
return nil, err
}
user := c.GetString("user")
if client.IsAdmin(user) {
user = ""
}
msg := `GET hosts
Stats: state >= 0

@ -65,6 +65,9 @@ func v1TableGet(c *gin.Context, params *v1TableGetParams) ([]gin.H, error) {
return nil, err
}
user := c.GetString("user")
if client.IsAdmin(user) {
user = ""
}
columns := ""
containsAll := false
@ -129,6 +132,9 @@ func v1TableGetColumns(c *gin.Context, params *v1TableGetColumnsParams) ([]strin
return nil, err
}
user := c.GetString("user")
if client.IsAdmin(user) {
user = ""
}
msg := fmt.Sprintf("GET columns\nColumns: name\nFilter: table = %s", params.Table)
resp, err := client.Request(c, msg, user, 0)

Loading…
Cancel
Save