Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LXD support #143

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Udica supports following container engines:
* docker v1.13+
* podman v2.0+
* containerd v1.5.0+ (using `nerdctl` v0.14+ or crictl)
* LXD v5.21.1+

## Installing

Expand Down
402 changes: 402 additions & 0 deletions tests/test_basic.lxd.cil

Large diffs are not rendered by default.

92 changes: 92 additions & 0 deletions tests/test_basic.lxd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"architecture": "x86_64",
"config": {
"image.architecture": "amd64",
"image.description": "ubuntu 20.04 LTS amd64 (release) (20240626)",
"image.label": "release",
"image.os": "ubuntu",
"image.release": "focal",
"image.serial": "20240626",
"image.type": "squashfs",
"image.version": "20.04",
"volatile.base_image": "afeb6fc84380878e47e1be18b3cd4e0a6671610f94ad3ffc8a50481afbe77a19",
"volatile.cloud-init.instance-id": "402d6e74-3ce0-483c-8d46-fdb28cdab306",
"volatile.eth0.host_name": "veth21ca03ab",
"volatile.eth0.hwaddr": "00:16:3e:fc:a1:ec",
"volatile.idmap.base": "0",
"volatile.idmap.current": "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000}]",
"volatile.idmap.next": "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000}]",
"volatile.last_state.idmap": "[]",
"volatile.last_state.power": "RUNNING",
"volatile.last_state.ready": "false",
"volatile.uuid": "5835af62-6142-4a7c-9282-35d31ae22cb7",
"volatile.uuid.generation": "5835af62-6142-4a7c-9282-35d31ae22cb7"
},
"created_at": "2024-06-29T16:03:00.781248448Z",
"description": "",
"devices": {},
"ephemeral": false,
"expanded_config": {
"image.architecture": "amd64",
"image.description": "ubuntu 20.04 LTS amd64 (release) (20240626)",
"image.label": "release",
"image.os": "ubuntu",
"image.release": "focal",
"image.serial": "20240626",
"image.type": "squashfs",
"image.version": "20.04",
"volatile.base_image": "afeb6fc84380878e47e1be18b3cd4e0a6671610f94ad3ffc8a50481afbe77a19",
"volatile.cloud-init.instance-id": "402d6e74-3ce0-483c-8d46-fdb28cdab306",
"volatile.eth0.host_name": "veth21ca03ab",
"volatile.eth0.hwaddr": "00:16:3e:fc:a1:ec",
"volatile.idmap.base": "0",
"volatile.idmap.current": "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000}]",
"volatile.idmap.next": "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000}]",
"volatile.last_state.idmap": "[]",
"volatile.last_state.power": "RUNNING",
"volatile.last_state.ready": "false",
"volatile.uuid": "5835af62-6142-4a7c-9282-35d31ae22cb7",
"volatile.uuid.generation": "5835af62-6142-4a7c-9282-35d31ae22cb7"
},
"expanded_devices": {
"eth0": {
"name": "eth0",
"network": "lxdbr0",
"type": "nic"
},
"home": {
"path": "/home",
"readonly": "true",
"source": "/home",
"type": "disk"
},
"myport21": {
"bind": "host",
"connect": "tcp:127.0.0.1:21",
"listen": "tcp:0.0.0.0:21",
"type": "proxy"
},
"root": {
"path": "/",
"pool": "default",
"type": "disk"
},
"spool": {
"path": "/var/spool",
"source": "/var/spool",
"type": "disk"
}
},
"last_used_at": "2024-07-01T15:00:22.55170503Z",
"location": "none",
"name": "my-ubuntu-container",
"profiles": [
"default",
"myprofile"
],
"project": "default",
"stateful": false,
"status": "Running",
"status_code": 103,
"type": "container"
}
23 changes: 15 additions & 8 deletions udica/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

# import udica
from udica.parse import parse_avc_file
from udica.parse import ENGINE_ALL, ENGINE_PODMAN, ENGINE_DOCKER
from udica.parse import ENGINE_ALL, ENGINE_PODMAN, ENGINE_DOCKER, ENGINE_LXD
from udica.version import version
from udica import parse
from udica.policy import create_policy, load_policy, generate_playbook
Expand Down Expand Up @@ -260,13 +260,20 @@ def main():

if opts["ContainerID"]:
container_inspect_raw = None
for backend in [ENGINE_PODMAN, ENGINE_DOCKER]:
for backend in [ENGINE_PODMAN, ENGINE_DOCKER, ENGINE_LXD]:
try:
run_inspect = subprocess.Popen(
[backend, "inspect", opts["ContainerID"]],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
if backend == ENGINE_LXD:
run_inspect = subprocess.Popen(
["lxc", "query", "/1.0/instances/", opts["ContainerID"]],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
else:
run_inspect = subprocess.Popen(
[backend, "inspect", opts["ContainerID"]],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
inspect_data = run_inspect.communicate()[0]
if run_inspect.returncode != 0:
inspect_data = None
Expand All @@ -278,7 +285,7 @@ def main():
break

if not container_inspect_raw:
print("Container with specified ID does not exits!")
print("Container with specified ID does not exist!")
exit(3)

if opts["JsonFile"]:
Expand Down
88 changes: 75 additions & 13 deletions udica/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,15 @@
import abc
import json

#: Constant for the podman engine
#: Constants for container engines
ENGINE_PODMAN = "podman"

#: Constant for the cri-o engine
ENGINE_CRIO = "CRI-O"

#: Constant for the docker engine
ENGINE_DOCKER = "docker"

#: Constant for the containerd engine
ENGINE_CONTAINERD = "containerd"
ENGINE_LXD = "LXD"

#: All supported engines
ENGINE_ALL = [ENGINE_PODMAN, ENGINE_CRIO, ENGINE_DOCKER, ENGINE_CONTAINERD]
ENGINE_ALL = [ENGINE_PODMAN, ENGINE_CRIO, ENGINE_DOCKER, ENGINE_CONTAINERD, ENGINE_LXD]


# Decorator for verifying that getting value from "data" won't
Expand Down Expand Up @@ -69,17 +64,34 @@ def json_is_containerd_format(json_rep):

def json_is_podman_format(json_rep):
"""Check if the inspected file is in a format from podman."""
return isinstance(json_rep, list) and (
"container=oci" in json_rep[0]["Config"]["Env"]
or "container=podman" in json_rep[0]["Config"]["Env"]
return (
isinstance(json_rep, list)
and (
"container=oci" in json_rep[0]["Config"]["Env"]
or "container=podman" in json_rep[0]["Config"]["Env"]
)
)

def json_is_lxd_format(json_rep):
"""Check if the inspected file is in a format from LXD."""
return (
# LXD's inspection output returns a single dictionary for a single container
isinstance(json_rep, dict)
and "expanded_devices" in json_rep
and "architecture" in json_rep
and "config" in json_rep
)


def get_engine_helper(data, ContainerEngine):
engine = validate_container_engine(ContainerEngine)

if engine == "-":
json_rep = json.loads(data)
if json_is_list(json_rep):

if json_is_lxd_format(json_rep):
return LxdHelper()
elif json_is_list(json_rep):
if json_is_containerd_format(json_rep):
return ContainerdHelper()
elif json_is_podman_format(json_rep):
Expand All @@ -96,7 +108,9 @@ def get_engine_helper(data, ContainerEngine):
return CrioHelper()
elif engine == ENGINE_CONTAINERD:
return ContainerdHelper()
raise RuntimeError("Unkown engine")
elif engine == ENGINE_LXD:
return LxdHelper()
raise RuntimeError("Unknown engine")


class EngineHelper(abc.ABC):
Expand Down Expand Up @@ -259,6 +273,54 @@ def get_caps(self, data, opts):
return opts["Caps"].split(",")
return data[0]["Spec"]["process"]["capabilities"]["effective"]

class LxdHelper(EngineHelper):
def __init__(self):
super().__init__(ENGINE_LXD)

@getter_decorator
def get_devices(self, data):
# Extract devices from the config
devices = []
config_devices = data["expanded_devices"]
for name, device in config_devices.items():
if device["type"] in ["unix-block", "unix-char"]:
device["PathOnHost"] = device.get("path", "")
devices.append(device)
return devices

@getter_decorator
def get_mounts(self, data):
# Extract mounts (disk devices)
mounts = []
config_devices = data["expanded_devices"]
for name, device in config_devices.items():
if device["type"] == "disk":
mount = {
"source": device.get("source", ""),
"path": device.get("path", ""),
"readonly": device.get("readonly", False),
}
mounts.append(mount)
return mounts

@getter_decorator
def get_ports(self, data):
# Extract port information from the LXD JSON configuration
ports = []
for name, device in data["expanded_devices"].items():
if device["type"] == "proxy":
port_info = {
"portNumber": int(device["listen"].split(":")[-1]),
"protocol": device["connect"].split(":")[0]
}
ports.append(port_info)
return ports

@getter_decorator
def get_caps(self, data, opts):
# Capabilities are not part of LXD spec directly
return []


def parse_cap(data):
return data.decode().split("\n")[1].split(",")
Expand Down
2 changes: 2 additions & 0 deletions udica/perms.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

# Dictionary of permissions for various device and file types
perm = {
"device_rw": "getattr read write append ioctl lock open",
"dir_rw": "add_name create getattr ioctl lock open read remove_name rmdir search setattr write",
Expand All @@ -25,4 +26,5 @@
"socket_ro": "getattr open read",
}

# Dictionary of socket types mapped to their corresponding SELinux object class
socket = {"tcp": "tcp_socket", "udp": "udp_socket", "sctp": "sctp_socket"}
Loading