Skip to content

Commit

Permalink
cmd/enter: honor user’s configured shell inside toolbox
Browse files Browse the repository at this point in the history
Signed-off-by: Yann Soubeyrand <github@yann.soubeyrand.eu>
  • Loading branch information
yann-soubeyrand committed Sep 13, 2024
1 parent a653325 commit 1ffecee
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 93 deletions.
7 changes: 1 addition & 6 deletions src/cmd/enter.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,7 @@ func enter(cmd *cobra.Command, args []string) error {
return err
}

userShell := os.Getenv("SHELL")
if userShell == "" {
return errors.New("failed to get the current user's default shell")
}

command := []string{userShell, "-l"}
command := []string{"toolbox", "sh", "--", "-l"}

if err := runCommand(container, defaultContainer, image, release, 0, command, true, true, false); err != nil {
return err
Expand Down
193 changes: 106 additions & 87 deletions src/cmd/initContainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"io/ioutil"
"os"
"os/user"
"path"
"path/filepath"
"strconv"
"strings"
Expand Down Expand Up @@ -147,6 +148,8 @@ func init() {
}

func initContainer(cmd *cobra.Command, args []string) error {
const factoryInitializedFlag string = "/var/lib/toolbox/factory-initialized"

if !utils.IsInsideContainer() {
var builder strings.Builder
fmt.Fprintf(&builder, "the 'init-container' command can only be used inside containers\n")
Expand Down Expand Up @@ -189,82 +192,136 @@ func initContainer(cmd *cobra.Command, args []string) error {
return errors.New(errMsg)
}

if utils.PathExists("/run/host/etc") {
logrus.Debug("Path /run/host/etc exists")
if !utils.PathExists(factoryInitializedFlag) {
if utils.PathExists("/run/host/etc") {
logrus.Debug("Path /run/host/etc exists")

if _, err := os.Readlink("/etc/host.conf"); err != nil {
if err := redirectPath("/etc/host.conf",
"/run/host/etc/host.conf",
false); err != nil {
return err
if _, err := os.Readlink("/etc/host.conf"); err != nil {
if err := redirectPath("/etc/host.conf",
"/run/host/etc/host.conf",
false); err != nil {
return err
}
}
}

if _, err := os.Readlink("/etc/hosts"); err != nil {
if err := redirectPath("/etc/hosts",
"/run/host/etc/hosts",
false); err != nil {
return err
if _, err := os.Readlink("/etc/hosts"); err != nil {
if err := redirectPath("/etc/hosts",
"/run/host/etc/hosts",
false); err != nil {
return err
}
}
}

if localtimeTarget, err := os.Readlink("/etc/localtime"); err != nil ||
localtimeTarget != "/run/host/etc/localtime" {
if err := redirectPath("/etc/localtime",
"/run/host/etc/localtime",
false); err != nil {
if localtimeTarget, err := os.Readlink("/etc/localtime"); err != nil ||
localtimeTarget != "/run/host/etc/localtime" {
if err := redirectPath("/etc/localtime",
"/run/host/etc/localtime",
false); err != nil {
return err
}
}

if err := updateTimeZoneFromLocalTime(); err != nil {
return err
}

if resolvConfTarget, err := os.Readlink("/etc/resolv.conf"); err != nil ||
resolvConfTarget != "/run/host/etc/resolv.conf" {
if err := redirectPath("/etc/resolv.conf",
"/run/host/etc/resolv.conf",
false); err != nil {
return err
}
}
}

if err := updateTimeZoneFromLocalTime(); err != nil {
return err
if initContainerFlags.mediaLink {
if _, err := os.Readlink("/media"); err != nil {
if err = redirectPath("/media", "/run/media", true); err != nil {
return err
}
}
}

if resolvConfTarget, err := os.Readlink("/etc/resolv.conf"); err != nil ||
resolvConfTarget != "/run/host/etc/resolv.conf" {
if err := redirectPath("/etc/resolv.conf",
"/run/host/etc/resolv.conf",
false); err != nil {
return err
if initContainerFlags.mntLink {
if _, err := os.Readlink("/mnt"); err != nil {
if err := redirectPath("/mnt", "/var/mnt", true); err != nil {
return err
}
}
}
}

if initContainerFlags.mediaLink {
if _, err := os.Readlink("/media"); err != nil {
if err = redirectPath("/media", "/run/media", true); err != nil {
for _, mount := range initContainerMounts {
if err := mountBind(mount.containerPath, mount.source, mount.flags); err != nil {
return err
}
}
}

if initContainerFlags.mntLink {
if _, err := os.Readlink("/mnt"); err != nil {
if err := redirectPath("/mnt", "/var/mnt", true); err != nil {
if utils.PathExists("/sys/fs/selinux") {
if err := mountBind("/sys/fs/selinux", "/usr/share/empty", ""); err != nil {
return err
}
}
}

for _, mount := range initContainerMounts {
if err := mountBind(mount.containerPath, mount.source, mount.flags); err != nil {
if err := configureUsers(initContainerFlags.uid,
initContainerFlags.user,
initContainerFlags.home,
initContainerFlags.shell,
initContainerFlags.homeLink); err != nil {
return err
}
}

if utils.PathExists("/sys/fs/selinux") {
if err := mountBind("/sys/fs/selinux", "/usr/share/empty", ""); err != nil {
return err
if utils.PathExists("/etc/krb5.conf.d") && !utils.PathExists("/etc/krb5.conf.d/kcm_default_ccache") {
logrus.Debug("Setting KCM as the default Kerberos credential cache")

kcmConfigString := `# Written by Toolbx
# https://github.com/containers/toolbox
#
# # To disable the KCM credential cache, comment out the following lines.
[libdefaults]
default_ccache_name = KCM:
`

kcmConfigBytes := []byte(kcmConfigString)
if err := ioutil.WriteFile("/etc/krb5.conf.d/kcm_default_ccache",
kcmConfigBytes,
0644); err != nil {
return errors.New("failed to set KCM as the default Kerberos credential cache")
}
}
}

if err := configureUsers(initContainerFlags.uid,
initContainerFlags.user,
initContainerFlags.home,
initContainerFlags.shell,
initContainerFlags.homeLink); err != nil {
return err
if utils.PathExists("/usr/lib/rpm/macros.d") {
logrus.Debug("Configuring RPM to ignore bind mounts")

var builder strings.Builder
fmt.Fprintf(&builder, "# Written by Toolbx\n")
fmt.Fprintf(&builder, "# https://github.com/containers/toolbox\n")
fmt.Fprintf(&builder, "\n")
fmt.Fprintf(&builder, "%%_netsharedpath /dev:/media:/mnt:/proc:/sys:/tmp:/var/lib/flatpak:/var/lib/libvirt\n")

rpmConfigString := builder.String()
rpmConfigBytes := []byte(rpmConfigString)
if err := ioutil.WriteFile("/usr/lib/rpm/macros.d/macros.toolbox",
rpmConfigBytes,
0644); err != nil {
return fmt.Errorf("failed to configure RPM to ignore bind mounts: %w", err)
}
}

logrus.Debugf("Creating factory initialization flag %s", factoryInitializedFlag)

err := os.MkdirAll(path.Base(factoryInitializedFlag), 0700)
if err != nil {
return errors.New("failed to create toolbox data directory")
}

factoryInitializedFlagFile, err := os.Create(factoryInitializedFlag)
if err != nil {
return errors.New("failed to create initialization flag")
}

defer factoryInitializedFlagFile.Close()
}

uidString := strconv.Itoa(initContainerFlags.uid)
Expand Down Expand Up @@ -297,44 +354,6 @@ func initContainer(cmd *cobra.Command, args []string) error {
}
}

if utils.PathExists("/etc/krb5.conf.d") && !utils.PathExists("/etc/krb5.conf.d/kcm_default_ccache") {
logrus.Debug("Setting KCM as the default Kerberos credential cache")

kcmConfigString := `# Written by Toolbx
# https://github.com/containers/toolbox
#
# # To disable the KCM credential cache, comment out the following lines.
[libdefaults]
default_ccache_name = KCM:
`

kcmConfigBytes := []byte(kcmConfigString)
if err := ioutil.WriteFile("/etc/krb5.conf.d/kcm_default_ccache",
kcmConfigBytes,
0644); err != nil {
return errors.New("failed to set KCM as the default Kerberos credential cache")
}
}

if utils.PathExists("/usr/lib/rpm/macros.d") {
logrus.Debug("Configuring RPM to ignore bind mounts")

var builder strings.Builder
fmt.Fprintf(&builder, "# Written by Toolbx\n")
fmt.Fprintf(&builder, "# https://github.com/containers/toolbox\n")
fmt.Fprintf(&builder, "\n")
fmt.Fprintf(&builder, "%%_netsharedpath /dev:/media:/mnt:/proc:/sys:/tmp:/var/lib/flatpak:/var/lib/libvirt\n")

rpmConfigString := builder.String()
rpmConfigBytes := []byte(rpmConfigString)
if err := ioutil.WriteFile("/usr/lib/rpm/macros.d/macros.toolbox",
rpmConfigBytes,
0644); err != nil {
return fmt.Errorf("failed to configure RPM to ignore bind mounts: %w", err)
}
}

logrus.Debug("Setting up daily ticker")

tickerDaily := time.NewTicker(24 * time.Hour)
Expand Down
101 changes: 101 additions & 0 deletions src/cmd/sh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright © 2022 – 2024 Yann Soubeyrand
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cmd

import (
"errors"
"fmt"
"os"
"os/user"
"strings"
"syscall"

"github.com/containers/toolbox/pkg/utils"
lcuser "github.com/opencontainers/runc/libcontainer/user"
"github.com/spf13/cobra"
)

var shCmd = &cobra.Command{
Use: "sh",
Short: "Launch user configured shell inside container",
Hidden: true,
RunE: sh,
}

func init() {
shCmd.SetHelpFunc(shHelp)
rootCmd.AddCommand(shCmd)
}

func sh(cmd *cobra.Command, args []string) error {
if !utils.IsInsideContainer() {
var builder strings.Builder
fmt.Fprintf(&builder, "the 'sh' command can only be used inside containers\n")
fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase)

errMsg := builder.String()
return errors.New(errMsg)
}

u, err := user.Current()
if err != nil {
return fmt.Errorf("failed to get current user: %s", err)
}

lcu, err := lcuser.LookupUser(u.Username)
if err != nil {
return fmt.Errorf("failed to lookup current user shell: %s", err)
}

for i, shell := range []string{
lcu.Shell,
"/bin/bash",
"/bin/sh",
} {
if i > 0 {
fmt.Fprintf(os.Stderr, "Falling back to %q\n", shell)
}

err = syscall.Exec(shell, args, os.Environ())
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to execute %q: %s\n", shell, err)
}
}

return fmt.Errorf("failed to execute a shell")
}

func shHelp(cmd *cobra.Command, args []string) {
if utils.IsInsideContainer() {
if !utils.IsInsideToolboxContainer() {
fmt.Fprintf(os.Stderr, "Error: this is not a toolbox container\n")
return
}

if _, err := utils.ForwardToHost(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
return
}

return
}

if err := showManual("toolbox-sh"); err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
return
}
}
1 change: 1 addition & 0 deletions src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/go-logfmt/logfmt v0.5.0
github.com/godbus/dbus/v5 v5.0.6
github.com/google/renameio/v2 v2.0.0
github.com/opencontainers/runc v1.1.14
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.3.0
github.com/spf13/viper v1.10.1
Expand Down
2 changes: 2 additions & 0 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w=
github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA=
github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
Expand Down

0 comments on commit 1ffecee

Please sign in to comment.