Browse Source

Add process, entrypoint, group and user creation inside the zone

layerset
Till Wegmueller 3 years ago
parent
commit
f3868a58e6
  1. 89
      cmd/buildhelper.go
  2. 1
      go.mod
  3. 2
      go.sum
  4. 4
      host/build.go
  5. 132
      image/build/build.go
  6. 36
      image/build/config.go
  7. 9
      smf/manifest.go
  8. 18
      smf/svccfg.go
  9. 32
      sysacct/group.go
  10. 136
      sysacct/user.go

89
cmd/buildhelper.go

@ -7,6 +7,9 @@ import (
"time"
"git.wegmueller.it/opencloud/opencloud/image/build"
"git.wegmueller.it/opencloud/opencloud/smf"
"git.wegmueller.it/opencloud/opencloud/sysacct"
"github.com/goodhosts/hostsfile"
"github.com/sirupsen/logrus"
)
@ -21,8 +24,8 @@ func main() {
logrus.Infof("running buildhelper for os %s(%s) version %s", runtime.GOOS, runtime.GOARCH, version)
// A zone is not completely booted when you can login.
// normaly people wont notice this due to the fact that a manually
// issued command takes seconds compared to this buildhelper which takes miliseconds to start
// normally people wont notice this due to the fact that a manually
// issued command takes seconds compared to this buildhelper which takes milliseconds to start
// due to that speed the network is not available when buildhelper runs
time.Sleep(10 * time.Second)
@ -30,12 +33,94 @@ func main() {
if err := json.NewDecoder(os.Stdin).Decode(&imageConfig); err != nil {
panic(err)
}
logrus.Info("installing sources in zone")
if err := build.InstallSourcesInsideZone(imageConfig); err != nil {
panic(err)
}
if imageConfig.Groups != nil {
logrus.Infof("Creating additional groups")
for _, g := range imageConfig.Groups {
logrus.Infof("Adding Groups %s to image", g.Name)
if err := sysacct.GroupAdd(g); err != nil {
panic(err)
}
}
}
if imageConfig.Users != nil {
logrus.Infof("Creating additional users")
for iter, u := range imageConfig.Users {
logrus.Infof("Adding User %s to image", u.Name)
var err error
u, err = sysacct.UserAdd(u)
if err != nil {
panic(err)
}
imageConfig.Users[iter] = u
}
}
logrus.Info("performing actions")
if err := build.PerformActions(imageConfig); err != nil {
panic(err)
}
if imageConfig.Command != nil {
if imageConfig.Command.User == nil {
imageConfig.Command.User = &sysacct.User{
Name: "process",
Gecos: "User to execute the Process in the Image",
Group: "process",
}
}
logrus.Infof("Adding Process User %s to image", imageConfig.Command.User)
u, err := sysacct.UserAdd(*imageConfig.Command.User)
if err != nil {
panic(err)
}
imageConfig.Command.User = &u
logrus.Infof("injecting command into image")
if err := build.CreateProcessInZoneSMF(&imageConfig); err != nil {
panic(err)
}
}
if imageConfig.EntryPoint != nil {
logrus.Infof("injecting entrypoint into image")
if err := build.CreateEntryPointInZone(&imageConfig); err != nil {
panic(err)
}
}
for _, svc := range imageConfig.Services {
logrus.Infof("Importing service description for %s", svc.Name)
if err := smf.Import(svc.Manifest); err != nil {
panic(err)
}
}
if imageConfig.Hosts != nil {
logrus.Infof("Adding hosts")
hosts, err := hostsfile.NewHosts()
if err != nil {
panic(err)
}
for _, h := range imageConfig.Hosts {
logrus.Infof("Adding Host entry %s->%s", h.Hostname, h.IPAddress)
if !hosts.Has(h.IPAddress, h.Hostname) {
if err := hosts.Add(h.IPAddress, h.Hostname); err != nil {
panic(err)
}
}
}
if err := hosts.Flush(); err != nil {
panic(err)
}
}
}

1
go.mod

@ -20,6 +20,7 @@ require (
github.com/garyburd/redigo v1.6.0 // indirect
github.com/go-test/deep v1.0.1
github.com/gofrs/uuid v3.2.0+incompatible // indirect
github.com/goodhosts/hostsfile v0.0.1 // indirect
github.com/gorilla/handlers v1.4.2 // indirect
github.com/gotestyourself/gotestyourself v1.4.0 // indirect
github.com/h2non/filetype v1.0.5

2
go.sum

@ -224,6 +224,8 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/goodhosts/hostsfile v0.0.1 h1:P5Q0c7noX7bVHkaw0m/6zoEah3/qvmmMQEk8p38oJIg=
github.com/goodhosts/hostsfile v0.0.1/go.mod h1:jmGrF+qLzAUdLZX1UDLvlRACntdvgHgqMiJa0du8C0Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=

4
host/build.go

@ -322,10 +322,10 @@ func (h *Host) ExportToImage(ref reference.Reference, container *pod.Container,
}
specImageConfig := specsv1.ImageConfig{
User: imageConfig.ServiceUser,
User: imageConfig.CommandUser,
ExposedPorts: toStringStruct(imageConfig.ExposedPorts),
Env: toStringSlice(imageConfig.Environment, "="),
Entrypoint: imageConfig.Enytrpoints,
Entrypoint: imageConfig.EntryPoint,
Cmd: imageConfig.Command,
WorkingDir: imageConfig.WorkingDir,
Labels: imageConfig.Labels,

132
image/build/build.go

@ -1,6 +1,7 @@
package build
import (
"encoding/xml"
"fmt"
"io"
"io/ioutil"
@ -8,10 +9,13 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"git.wegmueller.it/opencloud/opencloud/image/oci"
"git.wegmueller.it/opencloud/opencloud/smf"
"github.com/docker/docker/pkg/fileutils"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/ztrue/tracerr"
)
@ -358,3 +362,131 @@ func IsExecOther(mode os.FileMode) bool {
func IsExecAny(mode os.FileMode) bool {
return mode&0111 != 0
}
func CreateEntryPointInZone(cfg *Image) error {
if cfg.EntryPoint == nil {
return nil
}
p := specs.Process{
Terminal: false,
Env: toStringSlice(cfg.Environment, "="),
Args: cfg.EntryPoint,
NoNewPrivileges: true,
}
fullPath := filepath.Join("/lib/svc/manifest/application/entrypoint.xml")
manifestObj := smf.NewManifestFromOCIProcess("oci-entrypoint", smf.OCIEntryPointSVCName, cfg.Command.Milestone, p)
manifestObj.Services[0].Dependent = append(manifestObj.Services[0].Dependent, smf.Dependent{
Name: "oci-process",
Grouping: "require_all",
RestartOn: "none",
ServiceFmri: smf.ServiceFmri{
Value: smf.OCIProcesSVCName,
},
})
manifestObj.Services[0].PropertyGroup[0].PropVal[0].Value = "transient"
manFile, err := os.Create(fullPath)
if err != nil {
return tracerr.Wrap(err)
}
defer manFile.Close()
enc := xml.NewEncoder(manFile)
enc.Indent("", " ")
if err := enc.Encode(manifestObj); err != nil {
return tracerr.Wrap(err)
}
if err := smf.Import(fullPath); err != nil {
return tracerr.Wrap(err)
}
return nil
}
func CreateProcessInZoneSMF(cfg *Image) error {
if cfg.Command == nil {
return nil
}
p := specs.Process{
Terminal: false,
Env: toStringSlice(cfg.Environment, "="),
Args: cfg.Command.Args,
Cwd: cfg.Command.WorkingDir,
User: specs.User{UID: cfg.Command.User.UID, GID: cfg.Command.User.GID, AdditionalGids: cfg.Command.User.AdditionalGIDs},
NoNewPrivileges: true,
}
fullPath := filepath.Join("/lib/svc/manifest/application/oci-process.xml")
manifestObj := smf.NewManifestFromOCIProcess("oci-process", smf.OCIProcesSVCName, cfg.Command.Milestone, p)
if cfg.ExposedPorts != nil {
needsPrivPorts := false
for _, p := range cfg.ExposedPorts {
pNum, err := strconv.Atoi(p)
if err != nil {
continue
}
if pNum < 1024 {
needsPrivPorts = true
}
}
if needsPrivPorts {
manifestObj.Services[0].MethodContext.MethodCredential.Privileges = "default,net_privaddr"
}
}
manFile, err := os.Create(fullPath)
if err != nil {
return tracerr.Wrap(err)
}
defer manFile.Close()
enc := xml.NewEncoder(manFile)
enc.Indent("", " ")
if err := enc.Encode(manifestObj); err != nil {
return tracerr.Wrap(err)
}
if err := smf.Import(fullPath); err != nil {
return tracerr.Wrap(err)
}
return nil
}
func CreateProcessInZoneGoVisor(cfg *Image) error {
panic("Not implemented")
}
func volumeConfigToStringStruct(v []VolumeConfig) map[string]struct{} {
retVal := make(map[string]struct{})
for _, vol := range v {
retVal[vol.Path] = struct{}{}
}
return retVal
}
func toStringStruct(stringSlice []string) map[string]struct{} {
retVal := make(map[string]struct{})
for _, str := range stringSlice {
retVal[str] = struct{}{}
}
return retVal
}
func toStringSlice(stringMap map[string]string, seperator string) []string {
retVal := make([]string, len(stringMap))
i := 0
for key, value := range stringMap {
retVal[i] = fmt.Sprintf("%s%s%s", key, seperator, value)
i++
}
return retVal
}

36
image/build/config.go

@ -5,6 +5,7 @@ import (
"runtime"
"strings"
"git.wegmueller.it/opencloud/opencloud/sysacct"
"git.wegmueller.it/opencloud/opencloud/zfs"
"github.com/hashicorp/hcl2/gohcl"
"github.com/hashicorp/hcl2/hcl"
@ -81,24 +82,29 @@ type Image struct {
Name string `hcl:"name,label"`
BaseImage string `hcl:"base"`
OSFlavour string `hcl:"os,optional"`
Users []User `hcl:"user,block"`
Groups []Group `hcl:"group,block"`
Users []sysacct.User `hcl:"user,block"`
Groups []sysacct.Group `hcl:"group,block"`
Sources []SourceDescription `hcl:"source,block"`
CopyOperations []CopyOperation `hcl:"copy,block"`
Actions []Action `hcl:"action,block"`
Maintainer *MaintainerInformation `hcl:"maintainer,optional"`
Services []ServiceDescription `hcl:"service,block"` //TODO implement
Command []string `hcl:"cmd,optional"` //TODO implement with something like gvisor
Command *Command `hcl:"cmd,optional"`
ExposedPorts []string `hcl:"expose,optional"`
Environment map[string]string `hcl:"env,optional"`
Enytrpoints []string `hcl:"entrypoints,optional"`
EntryPoint []string `hcl:"entrypoint,optional"`
Volumes []VolumeConfig `hcl:"volume,block"`
Hosts []HostEntry `hcl:"host,block"` //TODO impelement
WorkingDir string `hcl:"workdir,optional"` //TODO implement
ServiceUser string `hcl:"user,optional"` //TODO implement
Hosts []HostEntry `hcl:"host,block"`
Labels map[string]string `hcl:"labels,optional"`
}
type Command struct {
Args []string `hcl:"cmd"` //Binary and arguments to run
User *sysacct.User `hcl:"user,optional"` //User to run the command as
WorkingDir string `hcl:"workdir,optional"` //Directory to chdir into before starting the process defaults to /
Milestone string `hcl:"milestone,optional"` //The milestone after which the process should start defaults to svc:/milestone/multi-user must be an fmri
}
type ServiceDescription struct {
Name string `hcl:"name,label"`
Manifest string `hcl:"manifest"`
@ -110,22 +116,6 @@ type HostEntry struct {
IPAddress string `hcl:"ip_address"`
}
type Group struct {
Description string `hcl:"description,label"`
GID uint64 `hcl:"gid,optional"`
Name string `hcl:"name"`
}
type User struct {
Name string `hcl:"name"`
Gecos string `hcl:"gecos,label"`
Group string `hcl:"group,optional"`
Shell string `hcl:"shell,optional"`
UID uint64 `hcl:"uid,optional"`
GID uint64 `hcl:"gid,optional"`
AdditionalGroups []string `hcl:"groups,optional"`
}
type VolumeConfig struct {
Name string `hcl:"name,optional"`
Description string `hcl:"description,optional"`

9
smf/manifest.go

@ -7,6 +7,9 @@ import (
"github.com/opencontainers/runtime-spec/specs-go"
)
const OCIProcesSVCName = "svc:/application/oci-process"
const OCIEntryPointSVCName = "svc:/application/oci-entrypoint"
func NewManifest(name string) ServiceBundle {
return ServiceBundle{
Type: "manifest",
@ -43,14 +46,14 @@ func NewProficeForOCIProcess(name string) ServiceBundle {
}
}
func NewManifestFromOCIProcess(name string, milestone string, process specs.Process) ServiceBundle {
func NewManifestFromOCIProcess(bundleName, svcName string, milestone string, process specs.Process) ServiceBundle {
if milestone == "" {
milestone = "svc:/milestone/multi-user"
}
return ServiceBundle{
Type: "manifest",
Name: "oci-process",
Name: bundleName,
Services: []Service{
{
Dependency: []Dependency{
@ -64,7 +67,7 @@ func NewManifestFromOCIProcess(name string, milestone string, process specs.Proc
},
},
},
Name: name,
Name: svcName,
CreateDefaultInstance: &CreateDefaultInstance{
Enabled: true,
},

18
smf/svccfg.go

@ -0,0 +1,18 @@
package smf
import (
"fmt"
"os/exec"
)
const svcCfgBin string = "/usr/sbin/svccfg"
func Import(path string) error {
c := exec.Command(svcCfgBin, "import", path)
output, err := c.CombinedOutput()
if err != nil {
return fmt.Errorf("command failed with: %s", output)
}
return nil
}

32
sysacct/group.go

@ -0,0 +1,32 @@
package sysacct
import (
"fmt"
"os/exec"
"strconv"
)
const groupAddBin = "/usr/sbin/groupadd"
type Group struct {
Description string `hcl:"description,label"`
GID uint32 `hcl:"gid,optional"`
Name string `hcl:"name"`
}
func GroupAdd(group Group) error {
args := make([]string, 0)
if group.GID > 0 {
args = append(args, "-g", strconv.Itoa(int(group.GID)))
}
args = append(args, group.Name)
c := exec.Command(groupAddBin, args...)
output, err := c.CombinedOutput()
if err != nil {
return fmt.Errorf("could not execute groupadd %s", output)
}
return nil
}

136
sysacct/user.go

@ -0,0 +1,136 @@
package sysacct
import (
"fmt"
"os/exec"
"os/user"
"strconv"
"strings"
"github.com/ztrue/tracerr"
)
const userAddBin = "/usr/sbin/useradd"
type User struct {
Name string `hcl:"name"`
Gecos string `hcl:"gecos,label"`
Group string `hcl:"group,optional"`
Shell string `hcl:"shell,optional"`
UID uint32 `hcl:"uid,optional"`
GID uint32 `hcl:"-,optional"`
AdditionalGroups []string `hcl:"groups,optional"`
AdditionalGIDs []uint32 `hcl:"-,optional"`
}
func UserAdd(u User) (User, error) {
if l, err := user.Lookup(u.Name); err == nil {
gid, err := strconv.Atoi(l.Gid)
if err != nil {
return u, tracerr.Wrap(err)
}
u.GID = uint32(gid)
uid, err := strconv.Atoi(l.Uid)
if err != nil {
return u, tracerr.Wrap(err)
}
u.UID = uint32(uid)
return u, nil
}
args := []string{"-c", u.Gecos}
if u.Group != "" {
args = append(args, "-g", u.Group)
lookupGroup, err := user.LookupGroup(u.Group)
if err == nil {
i, err := strconv.Atoi(lookupGroup.Gid)
if err != nil {
return u, tracerr.Wrap(err)
}
u.GID = uint32(i)
} else {
if err := GroupAdd(Group{Name: u.Group}); err != nil {
return u, tracerr.Wrap(err)
}
lookupGroup, _ = user.LookupGroup(u.Group)
i, err := strconv.Atoi(lookupGroup.Gid)
if err != nil {
return u, tracerr.Wrap(err)
}
u.GID = uint32(i)
}
}
if u.Shell != "" {
args = append(args, "-s", u.Shell)
}
if u.AdditionalGroups != nil {
args = append(args, "-G", strings.Join(u.AdditionalGroups, ","))
u.AdditionalGIDs = make([]uint32, 0)
for _, g := range u.AdditionalGroups {
lookupGroup, err := user.LookupGroup(g)
if err == nil {
i, err := strconv.Atoi(lookupGroup.Gid)
if err != nil {
return u, tracerr.Wrap(err)
}
u.AdditionalGIDs = append(u.AdditionalGIDs, uint32(i))
} else {
if err := GroupAdd(Group{Name: u.Group}); err != nil {
return u, tracerr.Wrap(err)
}
lookupGroup, _ = user.LookupGroup(u.Group)
i, err := strconv.Atoi(lookupGroup.Gid)
if err != nil {
return u, tracerr.Wrap(err)
}
u.AdditionalGIDs = append(u.AdditionalGIDs, uint32(i))
}
}
}
if u.UID > 0 {
args = append(args, "-u", strconv.Itoa(int(u.UID)))
}
args = append(args, u.Name)
c := exec.Command(userAddBin, args...)
output, err := c.CombinedOutput()
if err != nil {
return u, fmt.Errorf("could not execute useradd %s", output)
}
lookupUser, err := user.Lookup(u.Name)
if err != nil {
return u, tracerr.Wrap(err)
}
gid, err := strconv.Atoi(lookupUser.Gid)
if err != nil {
return u, tracerr.Wrap(err)
}
u.GID = uint32(gid)
uid, err := strconv.Atoi(lookupUser.Uid)
if err != nil {
return u, tracerr.Wrap(err)
}
u.UID = uint32(uid)
return u, nil
}
Loading…
Cancel
Save