LXC container support without LXD (#552)
* LXC container support without LXD * Fix: LXC required root privilege * Update README
This commit is contained in:
33
README.ja.md
33
README.ja.md
@@ -75,9 +75,10 @@ Table of Contents
|
||||
* [Example: Scan specific servers](#example-scan-specific-servers)
|
||||
* [Example: Scan via shell instead of SSH.](#example-scan-via-shell-instead-of-ssh)
|
||||
* [cronで動かす場合](#cronで動かす場合)
|
||||
* [Example: Scan containers (Docker/LXD)](#example-scan-containers-dockerlxd)
|
||||
* [Example: Scan containers (Docker/LXD/LXC)](#example-scan-containers-dockerlxdlxc)
|
||||
* [Docker](#docker)
|
||||
* [LXDコンテナをスキャンする場合](#lxdコンテナをスキャンする場合)
|
||||
* [LXCコンテナをスキャンする場合](#lxcコンテナをスキャンする場合)
|
||||
* [Usage: Report](#usage-report)
|
||||
* [How to read a report](#how-to-read-a-report)
|
||||
* [Example](#example-1)
|
||||
@@ -734,7 +735,7 @@ host = "172.31.4.82"
|
||||
# ["key", "value"],
|
||||
#]
|
||||
#[servers.172-31-4-82.containers]
|
||||
#type = "lxd" # or "docker"
|
||||
#type = "lxd" # or "docker" or "lxc"
|
||||
#includes = ["${running}"]
|
||||
#excludes = ["container_name", "container_id"]
|
||||
```
|
||||
@@ -819,7 +820,7 @@ host = "172.31.4.82"
|
||||
# ["key", "value"],
|
||||
#]
|
||||
#[servers.172-31-4-82.containers]
|
||||
#type = "lxd" # or "docker"
|
||||
#type = "lxd" # or "docker" or "lxc"
|
||||
#includes = ["${running}"]
|
||||
#excludes = ["container_name", "container_id"]
|
||||
```
|
||||
@@ -1103,7 +1104,7 @@ RHEL/CentOSの場合、スキャン対象サーバの/etc/sudoersに以下を追
|
||||
Defaults:vuls !requiretty
|
||||
```
|
||||
|
||||
## Example: Scan containers (Docker/LXD)
|
||||
## Example: Scan containers (Docker/LXD/LXC)
|
||||
|
||||
|
||||
コンテナはSSHデーモンを起動しないで運用するケースが一般的。
|
||||
@@ -1177,6 +1178,30 @@ type = "lxd"
|
||||
includes = ["${running}"]
|
||||
```
|
||||
|
||||
### LXC
|
||||
|
||||
Vulsは、ホストにSSHで接続し、`lxc-attach`でLXCコンテナにコマンドを発行して脆弱性をスキャンする。
|
||||
```
|
||||
[servers]
|
||||
|
||||
[servers.172-31-4-82]
|
||||
host = "172.31.4.82"
|
||||
user = "ec2-user"
|
||||
keyPath = "/home/username/.ssh/id_rsa"
|
||||
|
||||
[servers.172-31-4-82.containers]
|
||||
type = "lxc"
|
||||
includes = ["${running}"]
|
||||
```
|
||||
|
||||
LXCコンテナの操作にはroot権限が必要です。
|
||||
|
||||
スキャン対象サーバ上の`/etc/sudoers`のサンプル
|
||||
|
||||
```
|
||||
vuls ALL=(ALL) NOPASSWD:/usr/bin/lxc-attach -n *, /usr/bin/lxc-ls *
|
||||
```
|
||||
|
||||
# Usage: Report
|
||||
|
||||
```
|
||||
|
||||
35
README.md
35
README.md
@@ -81,9 +81,10 @@ Table of Contents
|
||||
* [Example: Scan specific servers](#example-scan-specific-servers)
|
||||
* [Example: Scan via shell instead of SSH.](#example-scan-via-shell-instead-of-ssh)
|
||||
* [cron](#cron)
|
||||
* [Example: Scan containers (Docker/LXD)](#example-scan-containers-dockerlxd)
|
||||
* [Example: Scan containers (Docker/LXD/LXC)](#example-scan-containers-dockerlxdlxc)
|
||||
* [Docker](#docker)
|
||||
* [LXD](#lxd)
|
||||
* [LXC](#lxc)
|
||||
* [Usage: Report](#usage-report)
|
||||
* [How to read a report](#how-to-read-a-report)
|
||||
* [Example](#example-1)
|
||||
@@ -747,7 +748,7 @@ host = "172.31.4.82"
|
||||
# ["key", "value"],
|
||||
#]
|
||||
#[servers.172-31-4-82.containers]
|
||||
#type = "lxd" # or "docker"
|
||||
#type = "lxd" # or "docker" or "lxc"
|
||||
#includes = ["${running}"]
|
||||
#excludes = ["container_name", "container_id"]
|
||||
```
|
||||
@@ -852,7 +853,7 @@ You can customize your configuration using this template.
|
||||
# ["key", "value"],
|
||||
#]
|
||||
#[servers.172-31-4-82.containers]
|
||||
#type = "lxd" # or "docker"
|
||||
#type = "lxd" # or "docker" or "lxc"
|
||||
#includes = ["${running}"]
|
||||
#excludes = ["container_name", "container_id"]
|
||||
```
|
||||
@@ -867,7 +868,7 @@ You can customize your configuration using this template.
|
||||
- cpeNames: see [Usage: Scan vulnerability of non-OS package](#usage-scan-vulnerability-of-non-os-package)
|
||||
- ignoreCves: CVE IDs that will not be reported. But output to JSON file.
|
||||
- optional: Add additional information to JSON report.
|
||||
- containers: see [Example: Scan containers (Docker/LXD)(#example-scan-containers-dockerlxd)
|
||||
- containers: see [Example: Scan containers (Docker/LXD/LXC)(#example-scan-containers-dockerlxdlxc)
|
||||
|
||||
Vuls supports two types of SSH. One is external command. The other is native go implementation. For details, see [-ssh-native-insecure option](#-ssh-native-insecure-option)
|
||||
|
||||
@@ -1108,7 +1109,7 @@ If you use local scan mode for cron jobs, don't forget to add below line to `/et
|
||||
Defaults:vuls !requiretty
|
||||
```
|
||||
|
||||
## Example: Scan containers (Docker/LXD)
|
||||
## Example: Scan containers (Docker/LXD/LXC)
|
||||
|
||||
It is common that keep containers running without SSHd daemon.
|
||||
see [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
|
||||
@@ -1182,6 +1183,30 @@ type = "lxd"
|
||||
includes = ["${running}"]
|
||||
```
|
||||
|
||||
### LXC
|
||||
|
||||
Vuls scans lxc via `lxc-attach` instead of SSH.
|
||||
```
|
||||
[servers]
|
||||
|
||||
[servers.172-31-4-82]
|
||||
host = "172.31.4.82"
|
||||
user = "ec2-user"
|
||||
keyPath = "/home/username/.ssh/id_rsa"
|
||||
|
||||
[servers.172-31-4-82.containers]
|
||||
type = "lxc"
|
||||
includes = ["${running}"]
|
||||
```
|
||||
|
||||
LXC required root privilege.
|
||||
|
||||
Example of /etc/sudoers on target servers
|
||||
|
||||
```
|
||||
vuls ALL=(ALL) NOPASSWD:/usr/bin/lxc-attach -n *, /usr/bin/lxc-ls *
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
# Usage: Report
|
||||
|
||||
42
scan/base.go
42
scan/base.go
@@ -110,6 +110,12 @@ func (l *base) allContainers() (containers []config.Container, err error) {
|
||||
return containers, err
|
||||
}
|
||||
return l.parseLxdPs(stdout)
|
||||
case "lxc":
|
||||
stdout, err := l.lxcPs("-1")
|
||||
if err != nil {
|
||||
return containers, err
|
||||
}
|
||||
return l.parseLxcPs(stdout)
|
||||
default:
|
||||
return containers, fmt.Errorf(
|
||||
"Not supported yet: %s", l.ServerInfo.Containers.Type)
|
||||
@@ -130,6 +136,12 @@ func (l *base) runningContainers() (containers []config.Container, err error) {
|
||||
return containers, err
|
||||
}
|
||||
return l.parseLxdPs(stdout)
|
||||
case "lxc":
|
||||
stdout, err := l.lxcPs("-1 --running")
|
||||
if err != nil {
|
||||
return containers, err
|
||||
}
|
||||
return l.parseLxcPs(stdout)
|
||||
default:
|
||||
return containers, fmt.Errorf(
|
||||
"Not supported yet: %s", l.ServerInfo.Containers.Type)
|
||||
@@ -150,6 +162,12 @@ func (l *base) exitedContainers() (containers []config.Container, err error) {
|
||||
return containers, err
|
||||
}
|
||||
return l.parseLxdPs(stdout)
|
||||
case "lxc":
|
||||
stdout, err := l.lxcPs("-1 --stopped")
|
||||
if err != nil {
|
||||
return containers, err
|
||||
}
|
||||
return l.parseLxcPs(stdout)
|
||||
default:
|
||||
return containers, fmt.Errorf(
|
||||
"Not supported yet: %s", l.ServerInfo.Containers.Type)
|
||||
@@ -174,6 +192,15 @@ func (l *base) lxdPs(option string) (string, error) {
|
||||
return r.Stdout, nil
|
||||
}
|
||||
|
||||
func (l *base) lxcPs(option string) (string, error) {
|
||||
cmd := fmt.Sprintf("lxc-ls %s 2>/dev/null", option)
|
||||
r := l.exec(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
return "", fmt.Errorf("failed to SSH: %s", r)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
|
||||
func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) {
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
@@ -214,6 +241,21 @@ func (l *base) parseLxdPs(stdout string) (containers []config.Container, err err
|
||||
return
|
||||
}
|
||||
|
||||
func (l *base) parseLxcPs(stdout string) (containers []config.Container, err error) {
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 0 {
|
||||
break
|
||||
}
|
||||
containers = append(containers, config.Container{
|
||||
ContainerID: fields[0],
|
||||
Name: fields[0],
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *base) detectPlatform() {
|
||||
ok, instanceID, err := l.detectRunningOnAws()
|
||||
if err != nil {
|
||||
|
||||
@@ -365,8 +365,16 @@ func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
|
||||
cmd = fmt.Sprintf(`docker exec --user 0 %s %s -c '%s'`,
|
||||
c.Container.ContainerID, dockerShell(c.Distro.Family), cmd)
|
||||
case "lxd":
|
||||
// If the user belong to the "lxd" group, root privilege is not required.
|
||||
cmd = fmt.Sprintf(`lxc exec %s -- %s -c '%s'`,
|
||||
c.Container.Name, dockerShell(c.Distro.Family), cmd)
|
||||
case "lxc":
|
||||
cmd = fmt.Sprintf(`lxc-attach -n %s 2>/dev/null -- %s -c '%s'`,
|
||||
c.Container.Name, dockerShell(c.Distro.Family), cmd)
|
||||
// LXC required root privilege
|
||||
if c.User != "root" {
|
||||
cmd = fmt.Sprintf("sudo -S %s", cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
// cmd = fmt.Sprintf("set -x; %s", cmd)
|
||||
|
||||
@@ -46,21 +46,21 @@ func TestDecorateCmd(t *testing.T) {
|
||||
},
|
||||
// non-root sudo false
|
||||
{
|
||||
conf: config.ServerInfo{User: "non-roor"},
|
||||
conf: config.ServerInfo{User: "non-root"},
|
||||
cmd: "ls",
|
||||
sudo: false,
|
||||
expected: "ls",
|
||||
},
|
||||
// non-root sudo true
|
||||
{
|
||||
conf: config.ServerInfo{User: "non-roor"},
|
||||
conf: config.ServerInfo{User: "non-root"},
|
||||
cmd: "ls",
|
||||
sudo: true,
|
||||
expected: "sudo -S ls",
|
||||
},
|
||||
// non-root sudo true
|
||||
{
|
||||
conf: config.ServerInfo{User: "non-roor"},
|
||||
conf: config.ServerInfo{User: "non-root"},
|
||||
cmd: "ls | grep hoge",
|
||||
sudo: true,
|
||||
expected: "sudo -S ls | grep hoge",
|
||||
@@ -70,7 +70,7 @@ func TestDecorateCmd(t *testing.T) {
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc"},
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
},
|
||||
cmd: "ls",
|
||||
@@ -81,7 +81,7 @@ func TestDecorateCmd(t *testing.T) {
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc"},
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
},
|
||||
cmd: "ls",
|
||||
@@ -92,7 +92,7 @@ func TestDecorateCmd(t *testing.T) {
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc"},
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
},
|
||||
cmd: "ls",
|
||||
@@ -103,7 +103,7 @@ func TestDecorateCmd(t *testing.T) {
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc"},
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
},
|
||||
cmd: "ls",
|
||||
@@ -114,7 +114,7 @@ func TestDecorateCmd(t *testing.T) {
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc"},
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
},
|
||||
cmd: "ls | grep hoge",
|
||||
@@ -177,6 +177,62 @@ func TestDecorateCmd(t *testing.T) {
|
||||
sudo: true,
|
||||
expected: `lxc exec def -- /bin/sh -c 'ls | grep hoge'`,
|
||||
},
|
||||
// -------------lxc-------------
|
||||
// root sudo false lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: false,
|
||||
expected: `lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
|
||||
},
|
||||
// root sudo true lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: true,
|
||||
expected: `lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
|
||||
},
|
||||
// non-root sudo false, lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: false,
|
||||
expected: `sudo -S lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
|
||||
},
|
||||
// non-root sudo true, lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: true,
|
||||
expected: `sudo -S lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
|
||||
},
|
||||
// non-root sudo true lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
},
|
||||
cmd: "ls | grep hoge",
|
||||
sudo: true,
|
||||
expected: `sudo -S lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls | grep hoge'`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user