diff --git a/cmd/crowdsec-cli/dashboard.go b/cmd/crowdsec-cli/dashboard.go index af7317b18..c503e3e88 100644 --- a/cmd/crowdsec-cli/dashboard.go +++ b/cmd/crowdsec-cli/dashboard.go @@ -3,7 +3,10 @@ package main import ( "fmt" "os" + "os/exec" + "os/user" "path/filepath" + "strconv" "github.com/AlecAivazis/survey/v2" "github.com/crowdsecurity/crowdsec/pkg/metabase" @@ -24,6 +27,7 @@ var ( metabaseListenAddress = "127.0.0.1" metabaseListenPort = "3000" metabaseContainerID = "/crowdsec-metabase" + crowdsecGroup = "crowdsec" forceYes bool @@ -72,7 +76,48 @@ cscli dashboard setup -l 0.0.0.0 -p 443 --password if metabasePassword == "" { 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 { log.Fatalf(err.Error()) } @@ -92,6 +137,7 @@ cscli dashboard setup -l 0.0.0.0 -p 443 --password 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(&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().StringVar(&metabasePassword, "password", "", "metabase password") @@ -149,34 +195,44 @@ cscli dashboard remove --force log.Fatalf("unable to ask to force: %s", err) } } - if answer { if metabase.IsContainerExist(metabaseContainerID) { log.Debugf("Stopping container %s", metabaseContainerID) if err := metabase.StopContainer(metabaseContainerID); err != nil { 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) if err := metabase.RemoveContainer(metabaseContainerID); err != nil { log.Warningf("unable to remove container '%s': %s", metabaseContainerID, err) } 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 { log.Warningf("failed to remove metabase internal db : %s", err) } if force { - log.Debugf("Removing image %s", metabaseImage) - if err := metabase.RemoveImageContainer(metabaseImage); err != nil { - log.Warningf("Failed to remove metabase container %s : %s", metabaseImage, err) + if err := metabase.RemoveImageContainer(); err != nil { + log.Fatalf("removing docker image: %s", 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") cmdDashboard.AddCommand(cmdDashRemove) diff --git a/pkg/database/database.go b/pkg/database/database.go index 2681bd028..2daef7c69 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -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) } } 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) } } diff --git a/pkg/metabase/container.go b/pkg/metabase/container.go index 2b10c1472..da571b71f 100644 --- a/pkg/metabase/container.go +++ b/pkg/metabase/container.go @@ -16,29 +16,31 @@ import ( ) type Container struct { - ListenAddr string - ListenPort string - SharedFolder string - Image string - Name string - ID string - CLI *client.Client - MBDBUri string + ListenAddr string + ListenPort string + SharedFolder string + Image string + Name string + ID string + CLI *client.Client + 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()) if err != nil { return nil, fmt.Errorf("failed to create docker client : %s", err) } return &Container{ - ListenAddr: listenAddr, - ListenPort: listenPort, - SharedFolder: sharedFolder, - Image: image, - Name: name, - CLI: cli, - MBDBUri: mbDBURI, + ListenAddr: listenAddr, + ListenPort: listenPort, + SharedFolder: sharedFolder, + Image: image, + Name: name, + CLI: cli, + MBDBUri: mbDBURI, + DockerGroupID: dockerGroupID, }, nil } @@ -84,12 +86,12 @@ func (c *Container) Create() error { env = append(env, c.MBDBUri) } + env = append(env, fmt.Sprintf("MGID=%s", c.DockerGroupID)) dockerConfig := &container.Config{ Image: c.Image, Tty: true, Env: env, } - os := runtime.GOOS switch os { case "linux": @@ -158,15 +160,15 @@ func RemoveContainer(name string) error { return nil } -func RemoveImageContainer(image string) error { +func RemoveImageContainer() error { cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return fmt.Errorf("failed to create docker client : %s", err) } ctx := context.Background() - log.Printf("Removing docker metabase %s", image) - if err := cli.ContainerRemove(ctx, image, types.ContainerRemoveOptions{}); err != nil { - return fmt.Errorf("failed remove container %s : %s", image, err) + log.Printf("Removing docker image '%s'", metabaseImage) + if _, err := cli.ImageRemove(ctx, metabaseImage, types.ImageRemoveOptions{}); err != nil { + return fmt.Errorf("failed remove image container %s : %s", metabaseImage, err) } return nil } diff --git a/pkg/metabase/metabase.go b/pkg/metabase/metabase.go index ff18a0c66..1b758c943 100644 --- a/pkg/metabase/metabase.go +++ b/pkg/metabase/metabase.go @@ -28,13 +28,14 @@ type Metabase struct { } type Config struct { - Database *csconfig.DatabaseCfg `yaml:"database"` - ListenAddr string `yaml:"listen_addr"` - ListenPort string `yaml:"listen_port"` - ListenURL string `yaml:"listen_url"` - Username string `yaml:"username"` - Password string `yaml:"password"` - DBPath string `yaml:"metabase_db_path"` + Database *csconfig.DatabaseCfg `yaml:"database"` + ListenAddr string `yaml:"listen_addr"` + ListenPort string `yaml:"listen_port"` + ListenURL string `yaml:"listen_url"` + Username string `yaml:"username"` + Password string `yaml:"password"` + DBPath string `yaml:"metabase_db_path"` + DockerGroupID string `yaml:"-"` } var ( @@ -70,7 +71,7 @@ func (m *Metabase) Init() error { } 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 { 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{ Config: &Config{ - Database: dbConfig, - ListenAddr: listenAddr, - ListenPort: listenPort, - Username: username, - Password: password, - ListenURL: fmt.Sprintf("http://%s:%s", listenAddr, listenPort), - DBPath: mbDBPath, + Database: dbConfig, + ListenAddr: listenAddr, + ListenPort: listenPort, + Username: username, + Password: password, + ListenURL: fmt.Sprintf("http://%s:%s", listenAddr, listenPort), + DBPath: mbDBPath, + DockerGroupID: dockerGroupID, }, } if err := metabase.Init(); err != nil { diff --git a/wizard.sh b/wizard.sh index da8e759df..6a2674170 100755 --- a/wizard.sh +++ b/wizard.sh @@ -302,47 +302,44 @@ check_cs_version () { NEW_PATCH_VERSION=$(echo $NEW_CS_VERSION | cut -d'.' -f3) 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 + 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 fi elif [[ $NEW_MINOR_VERSION -gt $CURRENT_MINOR_VERSION ]] ; then log_warn "new version ($NEW_CS_VERSION) is a minor upgrade !" - 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 + 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 fi fi elif [[ $NEW_PATCH_VERSION -gt $CURRENT_PATCH_VERSION ]] ; then log_warn "new version ($NEW_CS_VERSION) is a patch !" - 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 + 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 fi fi elif [[ $NEW_MINOR_VERSION -eq $CURRENT_MINOR_VERSION ]]; then 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 + 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 fi fi