Add DNS and security fields to DB

Also moves port mappings out of the SQL DB and into a file on
disk. These could get very sizable (hundred to thousands of
ports) so moving them out to a file will keep the DB small and
fast.

Finally, add a foreign key reference from container ID to
container state ID. This ensures we never get into an
inconsistent state where we have data in one table but not the
other.

Signed-off-by: Matthew Heon <matthew.heon@gmail.com>

Closes: #225
Approved by: baude
This commit is contained in:
Matthew Heon
2018-01-15 11:21:30 -05:00
committed by Atomic Bot
parent 2bfb31ddf4
commit 2e48c60bc5
4 changed files with 190 additions and 35 deletions

View File

@ -156,23 +156,28 @@ type ContainerConfig struct {
Mounts []string `json:"mounts,omitempty"` Mounts []string `json:"mounts,omitempty"`
// Security Config // Security Config
// Whether the container is privileged
Privileged bool `json:"privileged"`
// Whether to set the No New Privileges flag
NoNewPrivs bool `json:"noNewPrivs"`
// SELinux process label for container // SELinux process label for container
ProcessLabel string `json:"ProcessLabel,omitempty"` ProcessLabel string `json:"ProcessLabel,omitempty"`
// SELinux mount label for root filesystem // SELinux mount label for root filesystem
MountLabel string `json:"MountLabel,omitempty"` MountLabel string `json:"MountLabel,omitempty"`
// User and group to use in the container // User and group to use in the container
// Can be specified by name or UID/GID // Can be specified by name or UID/GID
User string `json:"user"` User string `json:"user,omitempty"`
// Namespace Config // Namespace Config
// IDs of container to share namespaces with // IDs of container to share namespaces with
// NetNsCtr conflicts with the CreateNetNS bool // NetNsCtr conflicts with the CreateNetNS bool
IPCNsCtr string `json:"ipcNsCtr"` IPCNsCtr string `json:"ipcNsCtr,omitempty"`
MountNsCtr string `json:"mountNsCtr"` MountNsCtr string `json:"mountNsCtr,omitempty"`
NetNsCtr string `json:"netNsCtr"` NetNsCtr string `json:"netNsCtr,omitempty"`
PIDNsCtr string `json:"pidNsCtr"` PIDNsCtr string `json:"pidNsCtr,omitempty"`
UserNsCtr string `json:"userNsCtr"` UserNsCtr string `json:"userNsCtr,omitempty"`
UTSNsCtr string `json:"utsNsCtr"` UTSNsCtr string `json:"utsNsCtr,omitempty"`
CgroupNsCtr string `json:"cgroupNsCtr,omitempty"`
// Network Config // Network Config
// CreateNetNS indicates that libpod should create and configure a new // CreateNetNS indicates that libpod should create and configure a new
@ -183,6 +188,18 @@ type ContainerConfig struct {
// namespace // namespace
// These are not used unless CreateNetNS is true // These are not used unless CreateNetNS is true
PortMappings []ocicni.PortMapping `json:"portMappings,omitempty"` PortMappings []ocicni.PortMapping `json:"portMappings,omitempty"`
// DNS servers to use in container resolv.conf
// Will override servers in host resolv if set
DNSServer []net.IP `json:"dnsServer,omitempty"`
// DNS Search domains to use in container resolv.conf
// Will override search domains in host resolv if set
DNSSearch []string `json:"dnsSearch,omitempty"`
// DNS options to be set in container resolv.conf
// With override options in host resolv if set
DNSOption []string `json:"dnsOption,omitempty"`
// Hosts to add in container
// Will be appended to host's host file
HostAdd []string `json:"hostsAdd,omitempty"`
// Misc Options // Misc Options
// Whether to keep container STDIN open // Whether to keep container STDIN open

View File

@ -15,7 +15,7 @@ import (
// DBSchema is the current DB schema version // DBSchema is the current DB schema version
// Increments every time a change is made to the database's tables // Increments every time a change is made to the database's tables
const DBSchema = 7 const DBSchema = 8
// SQLState is a state implementation backed by a persistent SQLite3 database // SQLState is a state implementation backed by a persistent SQLite3 database
type SQLState struct { type SQLState struct {
@ -284,7 +284,8 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) {
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ? ?, ?, ?, ?, ?,
?, ?, ?, ?
);` );`
addCtrState = `INSERT INTO containerState VALUES ( addCtrState = `INSERT INTO containerState VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
@ -306,9 +307,24 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) {
return errors.Wrapf(err, "error marshaling container %s mounts to JSON", ctr.ID()) return errors.Wrapf(err, "error marshaling container %s mounts to JSON", ctr.ID())
} }
portsJSON, err := json.Marshal(ctr.config.PortMappings) dnsServerJSON, err := json.Marshal(ctr.config.DNSServer)
if err != nil { if err != nil {
return errors.Wrapf(err, "error marshaling container %s port mappings to JSON", ctr.ID()) return errors.Wrapf(err, "error marshaling container %s DNS servers to JSON", ctr.ID())
}
dnsSearchJSON, err := json.Marshal(ctr.config.DNSSearch)
if err != nil {
return errors.Wrapf(err, "error marshaling container %s DNS search domains to JSON", ctr.ID())
}
dnsOptionJSON, err := json.Marshal(ctr.config.DNSOption)
if err != nil {
return errors.Wrapf(err, "error marshaling container %s DNS options to JSON", ctr.ID())
}
hostAddJSON, err := json.Marshal(ctr.config.HostAdd)
if err != nil {
return errors.Wrapf(err, "error marshaling container %s hosts to JSON", ctr.ID())
} }
labelsJSON, err := json.Marshal(ctr.config.Labels) labelsJSON, err := json.Marshal(ctr.config.Labels)
@ -321,6 +337,19 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) {
netNSPath = ctr.state.NetNS.Path() netNSPath = ctr.state.NetNS.Path()
} }
specJSON, err := json.Marshal(ctr.config.Spec)
if err != nil {
return errors.Wrapf(err, "error marshalling container %s spec to JSON", ctr.ID())
}
portsJSON := []byte{}
if len(ctr.config.PortMappings) > 0 {
portsJSON, err = json.Marshal(&ctr.config.PortMappings)
if err != nil {
return errors.Wrapf(err, "error marshalling container %s port mappings to JSON", ctr.ID())
}
}
tx, err := s.db.Begin() tx, err := s.db.Begin()
if err != nil { if err != nil {
return errors.Wrapf(err, "error beginning database transaction") return errors.Wrapf(err, "error beginning database transaction")
@ -348,6 +377,8 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) {
ctr.config.StaticDir, ctr.config.StaticDir,
string(mounts), string(mounts),
boolToSQL(ctr.config.Privileged),
boolToSQL(ctr.config.NoNewPrivs),
ctr.config.ProcessLabel, ctr.config.ProcessLabel,
ctr.config.MountLabel, ctr.config.MountLabel,
ctr.config.User, ctr.config.User,
@ -358,9 +389,13 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) {
stringToNullString(ctr.config.PIDNsCtr), stringToNullString(ctr.config.PIDNsCtr),
stringToNullString(ctr.config.UserNsCtr), stringToNullString(ctr.config.UserNsCtr),
stringToNullString(ctr.config.UTSNsCtr), stringToNullString(ctr.config.UTSNsCtr),
stringToNullString(ctr.config.CgroupNsCtr),
boolToSQL(ctr.config.CreateNetNS), boolToSQL(ctr.config.CreateNetNS),
string(portsJSON), string(dnsServerJSON),
string(dnsSearchJSON),
string(dnsOptionJSON),
string(hostAddJSON),
boolToSQL(ctr.config.Stdin), boolToSQL(ctr.config.Stdin),
string(labelsJSON), string(labelsJSON),
@ -392,10 +427,6 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) {
} }
// Save the container's runtime spec to disk // Save the container's runtime spec to disk
specJSON, err := json.Marshal(ctr.config.Spec)
if err != nil {
return errors.Wrapf(err, "error marshalling container %s spec to JSON", ctr.ID())
}
specPath := getSpecPath(s.specsDir, ctr.ID()) specPath := getSpecPath(s.specsDir, ctr.ID())
if err := ioutil.WriteFile(specPath, specJSON, 0750); err != nil { if err := ioutil.WriteFile(specPath, specJSON, 0750); err != nil {
return errors.Wrapf(err, "error saving container %s spec JSON to disk", ctr.ID()) return errors.Wrapf(err, "error saving container %s spec JSON to disk", ctr.ID())
@ -408,6 +439,21 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) {
} }
}() }()
// If the container has port mappings, save them to disk
if len(ctr.config.PortMappings) > 0 {
portPath := getPortsPath(s.specsDir, ctr.ID())
if err := ioutil.WriteFile(portPath, portsJSON, 0750); err != nil {
return errors.Wrapf(err, "error saving container %s port JSON to disk", ctr.ID())
}
defer func() {
if err != nil {
if err2 := os.Remove(portPath); err2 != nil {
logrus.Errorf("Error removing container %s JSON ports from state: %v", ctr.ID(), err2)
}
}
}()
}
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return errors.Wrapf(err, "error committing transaction to add container %s", ctr.ID()) return errors.Wrapf(err, "error committing transaction to add container %s", ctr.ID())
} }
@ -668,6 +714,15 @@ func (s *SQLState) RemoveContainer(ctr *Container) error {
return errors.Wrapf(err, "error removing JSON spec from state for container %s", ctr.ID()) return errors.Wrapf(err, "error removing JSON spec from state for container %s", ctr.ID())
} }
// Remove containers ports JSON from disk
// May not exist, so ignore os.IsNotExist
portsPath := getPortsPath(s.specsDir, ctr.ID())
if err := os.Remove(portsPath); err != nil {
if !os.IsNotExist(err) {
return errors.Wrapf(err, "error removing JSON ports from state for container %s", ctr.ID())
}
}
ctr.valid = false ctr.valid = false
return nil return nil

View File

@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"time" "time"
@ -178,6 +179,8 @@ func prepareDB(db *sql.DB) (err error) {
StaticDir TEXT NOT NULL, StaticDir TEXT NOT NULL,
Mounts TEXT NOT NULL, Mounts TEXT NOT NULL,
Privileged INTEGER NOT NULL,
NoNewPrivs INTEGER NOT NULL,
ProcessLabel TEXT NOT NULL, ProcessLabel TEXT NOT NULL,
MountLabel TEXT NOT NULL, MountLabel TEXT NOT NULL,
User TEXT NOT NULL, User TEXT NOT NULL,
@ -188,9 +191,13 @@ func prepareDB(db *sql.DB) (err error) {
PIDNsCtr TEXT, PIDNsCtr TEXT,
UserNsCtr TEXT, UserNsCtr TEXT,
UTSNsCtr TEXT, UTSNsCtr TEXT,
CgroupNsCtr TEXT,
CreateNetNS INTEGER NOT NULL, CreateNetNS INTEGER NOT NULL,
PortMappings TEXT NOT NULL, DNSServer TEXT NOT NULL,
DNSSearch TEXT NOT NULL,
DNSOption TEXT NOT NULL,
HostAdd TEXT NOT NULL,
Stdin INTEGER NOT NULL, Stdin INTEGER NOT NULL,
LabelsJSON TEXT NOT NULL, LabelsJSON TEXT NOT NULL,
@ -202,16 +209,20 @@ func prepareDB(db *sql.DB) (err error) {
CHECK (ImageVolumes IN (0, 1)), CHECK (ImageVolumes IN (0, 1)),
CHECK (ReadOnly IN (0, 1)), CHECK (ReadOnly IN (0, 1)),
CHECK (SHMSize>=0), CHECK (SHMSize>=0),
CHECK (Privileged IN (0, 1)),
CHECK (NoNewPrivs IN (0, 1)),
CHECK (CreateNetNS IN (0, 1)), CHECK (CreateNetNS IN (0, 1)),
CHECK (Stdin IN (0, 1)), CHECK (Stdin IN (0, 1)),
CHECK (StopSignal>=0), CHECK (StopSignal>=0),
FOREIGN KEY (Id) REFERENCES containerState(Id) DEFERRABLE INITIALLY DEFERRED
FOREIGN KEY (Pod) REFERENCES pod(Id) DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (Pod) REFERENCES pod(Id) DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (IPCNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (IPCNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (MountNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (MountNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (NetNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (NetNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (PIDNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (PIDNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (UserNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (UserNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (UTSNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED FOREIGN KEY (UTSNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (CgroupNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED
); );
` `
@ -283,6 +294,11 @@ func getSpecPath(specsDir, id string) string {
return filepath.Join(specsDir, id) return filepath.Join(specsDir, id)
} }
// Get filename for container port mappings on disk
func getPortsPath(specsDir, id string) string {
return filepath.Join(specsDir, id+"_ports")
}
// Convert a bool into SQL-readable format // Convert a bool into SQL-readable format
func boolToSQL(b bool) int { func boolToSQL(b bool) int {
if b { if b {
@ -347,6 +363,8 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) {
staticDir string staticDir string
mounts string mounts string
privileged int
noNewPrivs int
processLabel string processLabel string
mountLabel string mountLabel string
user string user string
@ -357,9 +375,13 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) {
pidNsCtrNullStr sql.NullString pidNsCtrNullStr sql.NullString
userNsCtrNullStr sql.NullString userNsCtrNullStr sql.NullString
utsNsCtrNullStr sql.NullString utsNsCtrNullStr sql.NullString
cgroupNsCtrNullStr sql.NullString
createNetNS int createNetNS int
portMappingsJSON string dnsServerJSON string
dnsSearchJSON string
dnsOptionJSON string
hostAddJSON string
stdin int stdin int
labelsJSON string labelsJSON string
@ -396,6 +418,8 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) {
&staticDir, &staticDir,
&mounts, &mounts,
&privileged,
&noNewPrivs,
&processLabel, &processLabel,
&mountLabel, &mountLabel,
&user, &user,
@ -406,9 +430,13 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) {
&pidNsCtrNullStr, &pidNsCtrNullStr,
&userNsCtrNullStr, &userNsCtrNullStr,
&utsNsCtrNullStr, &utsNsCtrNullStr,
&cgroupNsCtrNullStr,
&createNetNS, &createNetNS,
&portMappingsJSON, &dnsServerJSON,
&dnsSearchJSON,
&dnsOptionJSON,
&hostAddJSON,
&stdin, &stdin,
&labelsJSON, &labelsJSON,
@ -453,6 +481,8 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) {
ctr.config.ShmSize = shmSize ctr.config.ShmSize = shmSize
ctr.config.StaticDir = staticDir ctr.config.StaticDir = staticDir
ctr.config.Privileged = boolFromSQL(privileged)
ctr.config.NoNewPrivs = boolFromSQL(noNewPrivs)
ctr.config.ProcessLabel = processLabel ctr.config.ProcessLabel = processLabel
ctr.config.MountLabel = mountLabel ctr.config.MountLabel = mountLabel
ctr.config.User = user ctr.config.User = user
@ -463,6 +493,7 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) {
ctr.config.PIDNsCtr = stringFromNullString(pidNsCtrNullStr) ctr.config.PIDNsCtr = stringFromNullString(pidNsCtrNullStr)
ctr.config.UserNsCtr = stringFromNullString(userNsCtrNullStr) ctr.config.UserNsCtr = stringFromNullString(userNsCtrNullStr)
ctr.config.UTSNsCtr = stringFromNullString(utsNsCtrNullStr) ctr.config.UTSNsCtr = stringFromNullString(utsNsCtrNullStr)
ctr.config.CgroupNsCtr = stringFromNullString(cgroupNsCtrNullStr)
ctr.config.CreateNetNS = boolFromSQL(createNetNS) ctr.config.CreateNetNS = boolFromSQL(createNetNS)
@ -490,8 +521,20 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) {
return nil, errors.Wrapf(err, "error parsing container %s mounts JSON", id) return nil, errors.Wrapf(err, "error parsing container %s mounts JSON", id)
} }
if err := json.Unmarshal([]byte(portMappingsJSON), &ctr.config.PortMappings); err != nil { if err := json.Unmarshal([]byte(dnsServerJSON), &ctr.config.DNSServer); err != nil {
return nil, errors.Wrapf(err, "error parsing container %s port mappings JSON", id) return nil, errors.Wrapf(err, "error parsing container %s DNS server JSON", id)
}
if err := json.Unmarshal([]byte(dnsSearchJSON), &ctr.config.DNSSearch); err != nil {
return nil, errors.Wrapf(err, "error parsing container %s DNS search JSON", id)
}
if err := json.Unmarshal([]byte(dnsOptionJSON), &ctr.config.DNSOption); err != nil {
return nil, errors.Wrapf(err, "error parsing container %s DNS option JSON", id)
}
if err := json.Unmarshal([]byte(hostAddJSON), &ctr.config.HostAdd); err != nil {
return nil, errors.Wrapf(err, "error parsing container %s DNS server JSON", id)
} }
labels := make(map[string]string) labels := make(map[string]string)
@ -550,5 +593,25 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) {
} }
ctr.config.Spec = ociSpec ctr.config.Spec = ociSpec
// Retrieve the ports from disk
// They may not exist - if they don't, this container just doesn't have ports
portPath := getPortsPath(s.specsDir, id)
_, err = os.Stat(portPath)
if err != nil {
if !os.IsNotExist(err) {
return nil, errors.Wrapf(err, "error stating container %s JSON ports", id)
}
}
if err == nil {
// The file exists, read it
fileContents, err := ioutil.ReadFile(portPath)
if err != nil {
return nil, errors.Wrapf(err, "error reading container %s JSON ports", id)
}
if err := json.Unmarshal(fileContents, &ctr.config.PortMappings); err != nil {
return nil, errors.Wrapf(err, "error parsing container %s JSON ports", id)
}
}
return ctr, nil return ctr, nil
} }

View File

@ -3,6 +3,7 @@ package libpod
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -10,6 +11,7 @@ import (
"time" "time"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/runtime-tools/generate"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -29,6 +31,24 @@ func getTestContainer(id, name, locksDir string) (*Container, error) {
StopSignal: 0, StopSignal: 0,
StopTimeout: 0, StopTimeout: 0,
CreatedTime: time.Now(), CreatedTime: time.Now(),
Privileged: true,
Mounts: []string{"/does/not/exist"},
DNSServer: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.2.2")},
DNSSearch: []string{"example.com", "example.example.com"},
PortMappings: []ocicni.PortMapping{
{
HostPort: 80,
ContainerPort: 90,
Protocol: "tcp",
HostIP: "192.168.3.3",
},
{
HostPort: 100,
ContainerPort: 110,
Protocol: "udp",
HostIP: "192.168.4.4",
},
},
}, },
state: &containerRuntimeInfo{ state: &containerRuntimeInfo{
State: ContainerStateRunning, State: ContainerStateRunning,