create crowdsec group for metabase and crowdsec.db (#606)

This commit is contained in:
AlteredCoder 2021-02-10 09:23:33 +01:00 committed by GitHub
parent 260332c726
commit dae4458a6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 66 deletions

View file

@ -3,7 +3,10 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"os/user"
"path/filepath" "path/filepath"
"strconv"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/crowdsecurity/crowdsec/pkg/metabase" "github.com/crowdsecurity/crowdsec/pkg/metabase"
@ -24,6 +27,7 @@ var (
metabaseListenAddress = "127.0.0.1" metabaseListenAddress = "127.0.0.1"
metabaseListenPort = "3000" metabaseListenPort = "3000"
metabaseContainerID = "/crowdsec-metabase" metabaseContainerID = "/crowdsec-metabase"
crowdsecGroup = "crowdsec"
forceYes bool forceYes bool
@ -72,7 +76,48 @@ cscli dashboard setup -l 0.0.0.0 -p 443 --password <password>
if metabasePassword == "" { if metabasePassword == "" {
metabasePassword = generatePassword(16) metabasePassword = generatePassword(16)
} }
mb, err := metabase.SetupMetabase(csConfig.API.Server.DbConfig, metabaseListenAddress, metabaseListenPort, metabaseUser, metabasePassword, metabaseDbPath) var answer bool
groupExist := false
dockerGroup, err := user.LookupGroup(crowdsecGroup)
if err == nil {
groupExist = true
}
if !forceYes && !groupExist {
prompt := &survey.Confirm{
Message: fmt.Sprintf("For metabase docker to be able to access SQLite file we need to add a new group called '%s' to the system, is it ok for you ?", crowdsecGroup),
Default: true,
}
if err := survey.AskOne(prompt, &answer); err != nil {
log.Fatalf("unable to ask to force: %s", err)
}
}
if !answer && !forceYes && !groupExist {
log.Fatalf("unable to continue without creating '%s' group", crowdsecGroup)
}
if !groupExist {
groupAddCmd, err := exec.LookPath("groupadd")
if err != nil {
log.Fatalf("unable to find 'groupadd' command, can't continue")
}
groupAdd := &exec.Cmd{Path: groupAddCmd, Args: []string{groupAddCmd, crowdsecGroup}}
if err := groupAdd.Run(); err != nil {
log.Fatalf("unable to add group '%s': %s", dockerGroup, err)
}
dockerGroup, err = user.LookupGroup(crowdsecGroup)
if err != nil {
log.Fatalf("unable to lookup '%s' group: %+v", dockerGroup, err)
}
}
intID, err := strconv.Atoi(dockerGroup.Gid)
if err != nil {
log.Fatalf("unable to convert group ID to int: %s", err)
}
if err := os.Chown(csConfig.DbConfig.DbPath, 0, intID); err != nil {
log.Fatalf("unable to chown sqlite db file '%s': %s", csConfig.DbConfig.DbPath, err)
}
mb, err := metabase.SetupMetabase(csConfig.API.Server.DbConfig, metabaseListenAddress, metabaseListenPort, metabaseUser, metabasePassword, metabaseDbPath, dockerGroup.Gid)
if err != nil { if err != nil {
log.Fatalf(err.Error()) log.Fatalf(err.Error())
} }
@ -92,6 +137,7 @@ cscli dashboard setup -l 0.0.0.0 -p 443 --password <password>
cmdDashSetup.Flags().StringVarP(&metabaseDbPath, "dir", "d", "", "Shared directory with metabase container.") cmdDashSetup.Flags().StringVarP(&metabaseDbPath, "dir", "d", "", "Shared directory with metabase container.")
cmdDashSetup.Flags().StringVarP(&metabaseListenAddress, "listen", "l", metabaseListenAddress, "Listen address of container") cmdDashSetup.Flags().StringVarP(&metabaseListenAddress, "listen", "l", metabaseListenAddress, "Listen address of container")
cmdDashSetup.Flags().StringVarP(&metabaseListenPort, "port", "p", metabaseListenPort, "Listen port of container") cmdDashSetup.Flags().StringVarP(&metabaseListenPort, "port", "p", metabaseListenPort, "Listen port of container")
cmdDashSetup.Flags().BoolVarP(&forceYes, "yes", "y", false, "force yes")
//cmdDashSetup.Flags().StringVarP(&metabaseUser, "user", "u", "crowdsec@crowdsec.net", "metabase user") //cmdDashSetup.Flags().StringVarP(&metabaseUser, "user", "u", "crowdsec@crowdsec.net", "metabase user")
cmdDashSetup.Flags().StringVar(&metabasePassword, "password", "", "metabase password") cmdDashSetup.Flags().StringVar(&metabasePassword, "password", "", "metabase password")
@ -149,34 +195,44 @@ cscli dashboard remove --force
log.Fatalf("unable to ask to force: %s", err) log.Fatalf("unable to ask to force: %s", err)
} }
} }
if answer { if answer {
if metabase.IsContainerExist(metabaseContainerID) { if metabase.IsContainerExist(metabaseContainerID) {
log.Debugf("Stopping container %s", metabaseContainerID) log.Debugf("Stopping container %s", metabaseContainerID)
if err := metabase.StopContainer(metabaseContainerID); err != nil { if err := metabase.StopContainer(metabaseContainerID); err != nil {
log.Warningf("unable to stop container '%s': %s", metabaseContainerID, err) log.Warningf("unable to stop container '%s': %s", metabaseContainerID, err)
} }
dockerGroup, err := user.LookupGroup(crowdsecGroup)
if err == nil { // if group exist, remove it
groupDelCmd, err := exec.LookPath("groupdel")
if err != nil {
log.Fatalf("unable to find 'groupdel' command, can't continue")
}
groupDel := &exec.Cmd{Path: groupDelCmd, Args: []string{groupDelCmd, crowdsecGroup}}
if err := groupDel.Run(); err != nil {
log.Errorf("unable to delete group '%s': %s", dockerGroup, err)
}
}
log.Debugf("Removing container %s", metabaseContainerID) log.Debugf("Removing container %s", metabaseContainerID)
if err := metabase.RemoveContainer(metabaseContainerID); err != nil { if err := metabase.RemoveContainer(metabaseContainerID); err != nil {
log.Warningf("unable to remove container '%s': %s", metabaseContainerID, err) log.Warningf("unable to remove container '%s': %s", metabaseContainerID, err)
} }
log.Infof("container %s stopped & removed", metabaseContainerID) log.Infof("container %s stopped & removed", metabaseContainerID)
} }
log.Debugf("Removing database %s", csConfig.ConfigPaths.DataDir) log.Debugf("Removing metabase db %s", csConfig.ConfigPaths.DataDir)
if err := metabase.RemoveDatabase(csConfig.ConfigPaths.DataDir); err != nil { if err := metabase.RemoveDatabase(csConfig.ConfigPaths.DataDir); err != nil {
log.Warningf("failed to remove metabase internal db : %s", err) log.Warningf("failed to remove metabase internal db : %s", err)
} }
if force { if force {
log.Debugf("Removing image %s", metabaseImage) if err := metabase.RemoveImageContainer(); err != nil {
if err := metabase.RemoveImageContainer(metabaseImage); err != nil { log.Fatalf("removing docker image: %s", err)
log.Warningf("Failed to remove metabase container %s : %s", metabaseImage, err)
} }
} }
} }
}, },
} }
cmdDashRemove.Flags().BoolVarP(&force, "force", "f", false, "Force remove : stop the container if running and remove.") cmdDashRemove.Flags().BoolVarP(&force, "force", "f", false, "Remove also the metabase image")
cmdDashRemove.Flags().BoolVarP(&forceYes, "yes", "y", false, "force yes") cmdDashRemove.Flags().BoolVarP(&forceYes, "yes", "y", false, "force yes")
cmdDashboard.AddCommand(cmdDashRemove) cmdDashboard.AddCommand(cmdDashRemove)

View file

@ -42,7 +42,7 @@ func NewClient(config *csconfig.DatabaseCfg) (*Client, error) {
return &Client{}, errors.Wrapf(err, "failed to create SQLite database file %q", config.DbPath) return &Client{}, errors.Wrapf(err, "failed to create SQLite database file %q", config.DbPath)
} }
} else { /*ensure file perms*/ } else { /*ensure file perms*/
if err := os.Chmod(config.DbPath, 0600); err != nil { if err := os.Chmod(config.DbPath, 0660); err != nil {
return &Client{}, fmt.Errorf("unable to set perms on %s: %v", config.DbPath, err) return &Client{}, fmt.Errorf("unable to set perms on %s: %v", config.DbPath, err)
} }
} }

View file

@ -16,29 +16,31 @@ import (
) )
type Container struct { type Container struct {
ListenAddr string ListenAddr string
ListenPort string ListenPort string
SharedFolder string SharedFolder string
Image string Image string
Name string Name string
ID string ID string
CLI *client.Client CLI *client.Client
MBDBUri string MBDBUri string
DockerGroupID string
} }
func NewContainer(listenAddr string, listenPort string, sharedFolder string, name string, image string, mbDBURI string) (*Container, error) { func NewContainer(listenAddr string, listenPort string, sharedFolder string, name string, image string, mbDBURI string, dockerGroupID string) (*Container, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create docker client : %s", err) return nil, fmt.Errorf("failed to create docker client : %s", err)
} }
return &Container{ return &Container{
ListenAddr: listenAddr, ListenAddr: listenAddr,
ListenPort: listenPort, ListenPort: listenPort,
SharedFolder: sharedFolder, SharedFolder: sharedFolder,
Image: image, Image: image,
Name: name, Name: name,
CLI: cli, CLI: cli,
MBDBUri: mbDBURI, MBDBUri: mbDBURI,
DockerGroupID: dockerGroupID,
}, nil }, nil
} }
@ -84,12 +86,12 @@ func (c *Container) Create() error {
env = append(env, c.MBDBUri) env = append(env, c.MBDBUri)
} }
env = append(env, fmt.Sprintf("MGID=%s", c.DockerGroupID))
dockerConfig := &container.Config{ dockerConfig := &container.Config{
Image: c.Image, Image: c.Image,
Tty: true, Tty: true,
Env: env, Env: env,
} }
os := runtime.GOOS os := runtime.GOOS
switch os { switch os {
case "linux": case "linux":
@ -158,15 +160,15 @@ func RemoveContainer(name string) error {
return nil return nil
} }
func RemoveImageContainer(image string) error { func RemoveImageContainer() error {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil { if err != nil {
return fmt.Errorf("failed to create docker client : %s", err) return fmt.Errorf("failed to create docker client : %s", err)
} }
ctx := context.Background() ctx := context.Background()
log.Printf("Removing docker metabase %s", image) log.Printf("Removing docker image '%s'", metabaseImage)
if err := cli.ContainerRemove(ctx, image, types.ContainerRemoveOptions{}); err != nil { if _, err := cli.ImageRemove(ctx, metabaseImage, types.ImageRemoveOptions{}); err != nil {
return fmt.Errorf("failed remove container %s : %s", image, err) return fmt.Errorf("failed remove image container %s : %s", metabaseImage, err)
} }
return nil return nil
} }

View file

@ -28,13 +28,14 @@ type Metabase struct {
} }
type Config struct { type Config struct {
Database *csconfig.DatabaseCfg `yaml:"database"` Database *csconfig.DatabaseCfg `yaml:"database"`
ListenAddr string `yaml:"listen_addr"` ListenAddr string `yaml:"listen_addr"`
ListenPort string `yaml:"listen_port"` ListenPort string `yaml:"listen_port"`
ListenURL string `yaml:"listen_url"` ListenURL string `yaml:"listen_url"`
Username string `yaml:"username"` Username string `yaml:"username"`
Password string `yaml:"password"` Password string `yaml:"password"`
DBPath string `yaml:"metabase_db_path"` DBPath string `yaml:"metabase_db_path"`
DockerGroupID string `yaml:"-"`
} }
var ( var (
@ -70,7 +71,7 @@ func (m *Metabase) Init() error {
} }
m.Database, err = NewDatabase(m.Config.Database, m.Client, remoteDBAddr) m.Database, err = NewDatabase(m.Config.Database, m.Client, remoteDBAddr)
m.Container, err = NewContainer(m.Config.ListenAddr, m.Config.ListenPort, m.Config.DBPath, containerName, metabaseImage, DBConnectionURI) m.Container, err = NewContainer(m.Config.ListenAddr, m.Config.ListenPort, m.Config.DBPath, containerName, metabaseImage, DBConnectionURI, m.Config.DockerGroupID)
if err != nil { if err != nil {
return errors.Wrap(err, "container init") return errors.Wrap(err, "container init")
} }
@ -123,16 +124,17 @@ func (m *Metabase) LoadConfig(configPath string) error {
} }
func SetupMetabase(dbConfig *csconfig.DatabaseCfg, listenAddr string, listenPort string, username string, password string, mbDBPath string) (*Metabase, error) { func SetupMetabase(dbConfig *csconfig.DatabaseCfg, listenAddr string, listenPort string, username string, password string, mbDBPath string, dockerGroupID string) (*Metabase, error) {
metabase := &Metabase{ metabase := &Metabase{
Config: &Config{ Config: &Config{
Database: dbConfig, Database: dbConfig,
ListenAddr: listenAddr, ListenAddr: listenAddr,
ListenPort: listenPort, ListenPort: listenPort,
Username: username, Username: username,
Password: password, Password: password,
ListenURL: fmt.Sprintf("http://%s:%s", listenAddr, listenPort), ListenURL: fmt.Sprintf("http://%s:%s", listenAddr, listenPort),
DBPath: mbDBPath, DBPath: mbDBPath,
DockerGroupID: dockerGroupID,
}, },
} }
if err := metabase.Init(); err != nil { if err := metabase.Init(); err != nil {

View file

@ -302,47 +302,44 @@ check_cs_version () {
NEW_PATCH_VERSION=$(echo $NEW_CS_VERSION | cut -d'.' -f3) NEW_PATCH_VERSION=$(echo $NEW_CS_VERSION | cut -d'.' -f3)
if [[ $NEW_MAJOR_VERSION -gt $CURRENT_MAJOR_VERSION ]]; then if [[ $NEW_MAJOR_VERSION -gt $CURRENT_MAJOR_VERSION ]]; then
log_warn "new version ($NEW_CS_VERSION) is a major, you need to follow documentation to upgrade !"
echo ""
echo "Please follow : https://docs.crowdsec.net/Crowdsec/v1/migration/"
if [[ ${FORCE_MODE} == "false" ]]; then if [[ ${FORCE_MODE} == "false" ]]; then
log_warn "new version ($NEW_CS_VERSION) is a major, you need to follow documentation to upgrade !"
echo ""
echo "Please follow : https://docs.crowdsec.net/Crowdsec/v1/migration/"
exit 1 exit 1
fi fi
elif [[ $NEW_MINOR_VERSION -gt $CURRENT_MINOR_VERSION ]] ; then elif [[ $NEW_MINOR_VERSION -gt $CURRENT_MINOR_VERSION ]] ; then
log_warn "new version ($NEW_CS_VERSION) is a minor upgrade !" log_warn "new version ($NEW_CS_VERSION) is a minor upgrade !"
if [[ $ACTION != "upgrade" ]] ; then if [[ $ACTION != "upgrade" ]] ; then
echo ""
echo "We recommand to upgrade with : sudo ./wizard.sh --upgrade "
echo "If you want to $ACTION anyway, please use '--force'."
echo ""
echo "Run : sudo ./wizard.sh --$ACTION --force"
if [[ ${FORCE_MODE} == "false" ]]; then if [[ ${FORCE_MODE} == "false" ]]; then
echo ""
echo "We recommand to upgrade with : sudo ./wizard.sh --upgrade "
echo "If you want to $ACTION anyway, please use '--force'."
echo ""
echo "Run : sudo ./wizard.sh --$ACTION --force"
exit 1 exit 1
fi fi
fi fi
elif [[ $NEW_PATCH_VERSION -gt $CURRENT_PATCH_VERSION ]] ; then elif [[ $NEW_PATCH_VERSION -gt $CURRENT_PATCH_VERSION ]] ; then
log_warn "new version ($NEW_CS_VERSION) is a patch !" log_warn "new version ($NEW_CS_VERSION) is a patch !"
if [[ $ACTION != "binupgrade" ]] ; then if [[ $ACTION != "binupgrade" ]] ; then
echo ""
echo "We recommand to upgrade binaries only : sudo ./wizard.sh --binupgrade "
echo "If you want to $ACTION anyway, please use '--force'."
echo ""
echo "Run : sudo ./wizard.sh --$ACTION --force"
if [[ ${FORCE_MODE} == "false" ]]; then if [[ ${FORCE_MODE} == "false" ]]; then
echo ""
echo "We recommand to upgrade binaries only : sudo ./wizard.sh --binupgrade "
echo "If you want to $ACTION anyway, please use '--force'."
echo ""
echo "Run : sudo ./wizard.sh --$ACTION --force"
exit 1 exit 1
fi fi
fi fi
elif [[ $NEW_MINOR_VERSION -eq $CURRENT_MINOR_VERSION ]]; then elif [[ $NEW_MINOR_VERSION -eq $CURRENT_MINOR_VERSION ]]; then
log_warn "new version ($NEW_CS_VERSION) is same as current version ($CURRENT_CS_VERSION) !" log_warn "new version ($NEW_CS_VERSION) is same as current version ($CURRENT_CS_VERSION) !"
echo ""
echo "We recommand to $ACTION only if it's an higher version. "
echo "If it's an RC version (vX.X.X-rc) you can upgrade it using '--force'."
echo ""
echo "Run : sudo ./wizard.sh --$ACTION --force"
if [[ ${FORCE_MODE} == "false" ]]; then if [[ ${FORCE_MODE} == "false" ]]; then
echo ""
echo "We recommand to $ACTION only if it's an higher version. "
echo "If it's an RC version (vX.X.X-rc) you can upgrade it using '--force'."
echo ""
echo "Run : sudo ./wizard.sh --$ACTION --force"
exit 1 exit 1
fi fi
fi fi