diff --git a/Makefile.am b/Makefile.am index d787fff8b..a5bcebf90 100644 --- a/Makefile.am +++ b/Makefile.am @@ -130,7 +130,7 @@ dist-luarock: $(LUACRUN_ROCK) endif crun_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -D CRUN_LIBDIR="\"$(CRUN_LIBDIR)\"" -crun_SOURCES = src/crun.c src/run.c src/delete.c src/kill.c src/pause.c src/unpause.c src/spec.c \ +crun_SOURCES = src/crun.c src/run.c src/delete.c src/kill.c src/pause.c src/unpause.c src/oci_features.c src/spec.c \ src/exec.c src/list.c src/create.c src/start.c src/state.c src/update.c src/ps.c \ src/checkpoint.c src/restore.c src/libcrun/cloned_binary.c @@ -143,7 +143,7 @@ endif EXTRA_DIST = COPYING COPYING.libcrun README.md NEWS SECURITY.md rpm/crun.spec.in autogen.sh \ src/crun.h src/list.h src/run.h src/delete.h src/kill.h src/pause.h src/unpause.h \ - src/create.h src/start.h src/state.h src/exec.h src/spec.h src/update.h src/ps.h \ + src/create.h src/start.h src/state.h src/exec.h src/oci_features.h src/spec.h src/update.h src/ps.h \ src/checkpoint.h src/restore.h src/libcrun/seccomp_notify.h src/libcrun/seccomp_notify_plugin.h \ src/libcrun/container.h src/libcrun/seccomp.h src/libcrun/ebpf.h \ src/libcrun/cgroup.h src/libcrun/cgroup-cgroupfs.h \ @@ -200,6 +200,7 @@ PYTHON_TESTS = tests/test_capabilities.py \ tests/test_devices.py \ tests/test_hostname.py \ tests/test_limits.py \ + tests/test_oci_features.py \ tests/test_mounts.py \ tests/test_paths.py \ tests/test_pid.py \ @@ -275,7 +276,7 @@ generate-mount_flags.c: src/libcrun/mount_flags.perf clang-format: # do not format files that were copied into the source directory. - git ls-files src tests | egrep "\\.[hc]" | grep -v "chroot_realpath.c\|cloned_binary.c\|signals.c\|mount_flags.c" | xargs clang-format -style=file -i + git ls-files src tests | grep -E "\\.[hc]" | grep -v "chroot_realpath.c\|cloned_binary.c\|signals.c\|mount_flags.c" | xargs clang-format -style=file -i shellcheck: shellcheck tests/*/*.sh contrib/*.sh diff --git a/src/crun.c b/src/crun.c index d96b6a151..7b3f831e6 100644 --- a/src/crun.c +++ b/src/crun.c @@ -45,6 +45,7 @@ #include "spec.h" #include "pause.h" #include "unpause.h" +#include "oci_features.h" #include "ps.h" #include "checkpoint.h" #include "restore.h" @@ -137,6 +138,7 @@ enum COMMAND_UPDATE, COMMAND_PAUSE, COMMAND_UNPAUSE, + COMMAND_FEATURES, COMMAND_PS, COMMAND_CHECKPOINT, COMMAND_RESTORE, @@ -155,6 +157,7 @@ struct commands_s commands[] = { { COMMAND_CREATE, "create", crun_command_create { COMMAND_UPDATE, "update", crun_command_update }, { COMMAND_PAUSE, "pause", crun_command_pause }, { COMMAND_UNPAUSE, "resume", crun_command_unpause }, + { COMMAND_FEATURES, "features", crun_command_features }, #if HAVE_CRIU && HAVE_DLOPEN { COMMAND_CHECKPOINT, "checkpoint", crun_command_checkpoint }, { COMMAND_RESTORE, "restore", crun_command_restore }, @@ -170,6 +173,7 @@ static char doc[] = "\nCOMMANDS:\n" "\tcreate - create a container\n" "\tdelete - remove definition for a container\n" "\texec - exec a command in a running container\n" + "\tfeatures - show the enabled features\n" "\tlist - list known containers\n" "\tkill - send a signal to the container init process\n" "\tps - show the processes in the container\n" diff --git a/src/libcrun/container.c b/src/libcrun/container.c index ff621bbc5..b9f2afd01 100644 --- a/src/libcrun/container.c +++ b/src/libcrun/container.c @@ -23,6 +23,7 @@ #include "container.h" #include "utils.h" #include "seccomp.h" +#include #include "scheduler.h" #include "seccomp_notify.h" #include "custom-handler.h" @@ -36,6 +37,7 @@ #include #include #include "status.h" +#include "mount_flags.h" #include "linux.h" #include "terminal.h" #include "io_priority.h" @@ -45,7 +47,9 @@ #include #include #include +#include #include +#include #ifdef HAVE_SYSTEMD # include @@ -92,6 +96,69 @@ struct sync_socket_message_s typedef runtime_spec_schema_defs_hook hook; +// linux hooks +char *hooks[] = { + "prestart", + "createRuntime", + "createContainer", + "startContainer", + "poststart", + "poststop" +}; + +// linux namespaces +static char *namespaces[] = { + "cgroup", + "ipc", + "mount", + "network", + "pid", + "user", + "uts" +}; + +static char *actions[] = { + "SCMP_ACT_ALLOW", + "SCMP_ACT_ERRNO", + "SCMP_ACT_KILL", + "SCMP_ACT_KILL_PROCESS", + "SCMP_ACT_KILL_THREAD", + "SCMP_ACT_LOG", + "SCMP_ACT_NOTIFY", + "SCMP_ACT_TRACE", + "SCMP_ACT_TRAP" +}; + +static char *operators[] = { + "SCMP_CMP_NE", + "SCMP_CMP_LT", + "SCMP_CMP_LE", + "SCMP_CMP_EQ", + "SCMP_CMP_GE", + "SCMP_CMP_GT", + "SCMP_CMP_MASKED_EQ", +}; + +static char *archs[] = { + "SCMP_ARCH_AARCH64", + "SCMP_ARCH_ARM", + "SCMP_ARCH_MIPS", + "SCMP_ARCH_MIPS64", + "SCMP_ARCH_MIPS64N32", + "SCMP_ARCH_MIPSEL", + "SCMP_ARCH_MIPSEL64", + "SCMP_ARCH_MIPSEL64N32", + "SCMP_ARCH_PPC", + "SCMP_ARCH_PPC64", + "SCMP_ARCH_PPC64LE", + "SCMP_ARCH_RISCV64", + "SCMP_ARCH_S390", + "SCMP_ARCH_S390X", + "SCMP_ARCH_X32", + "SCMP_ARCH_X86", + "SCMP_ARCH_X86_64" +}; + static const char spec_file[] = "\ {\n\ \"ociVersion\": \"1.0.0\",\n\ @@ -3706,6 +3773,165 @@ libcrun_container_update_from_values (libcrun_context_t *context, const char *id return ret; } +static void +populate_array_field (char ***field, char *array[], size_t num_elements) +{ + size_t i; + + *field = xmalloc0 ((num_elements + 1) * sizeof (char *)); + for (i = 0; i < num_elements; i++) + (*field)[i] = xstrdup (array[i]); + + (*field)[i] = NULL; +} + +static void +populate_capabilities (struct features_info_s *info, char ***capabilities, size_t *num_capabilities) +{ + size_t index = 0; + cap_value_t i; + char *endptr; + int j; + + *num_capabilities = 0; + for (i = 0;; i++) + { + char *v = cap_to_name (i); + if (v == NULL) + break; + strtol (v, &endptr, 10); + if (endptr != v) + { + // Non-numeric or non-zero value encountered, break the loop + break; + } + (*num_capabilities)++; + } + + *capabilities = xmalloc0 ((*num_capabilities + 1) * sizeof (const char *)); + for (i = 0; i < (cap_value_t) *num_capabilities; i++) + { + char *v = cap_to_name (i); + if (v == NULL) + break; + strtol (v, &endptr, 10); + if (endptr != v) + { + // Non-numeric or non-zero value encountered, break the loop + break; + } + + // Convert capability name to uppercase + for (j = 0; v[j] != '\0'; j++) + v[j] = toupper (v[j]); + + (*capabilities)[index] = v; + index++; + } + + (*capabilities)[index] = NULL; // Terminate the array with NULL + populate_array_field (&(info->linux.capabilities), *capabilities, *num_capabilities); +} + +static void +retrieve_mount_options (struct features_info_s **info) +{ + const struct propagation_flags_s *mount_options_list; + size_t num_mount_options = 0; + + // Retrieve mount options from wordlist + mount_options_list = get_mount_flags_from_wordlist (); + + // Calculate the number of mount options + while (mount_options_list[num_mount_options].name != NULL) + num_mount_options++; + + // Allocate memory for mount options in info struct + (*info)->mount_options = xmalloc0 ((num_mount_options + 1) * sizeof (char *)); + + // Copy mount options to info struct + for (size_t i = 0; i < num_mount_options; i++) + (*info)->mount_options[i] = xstrdup (mount_options_list[i].name); +} + +int +libcrun_container_get_features (libcrun_context_t *context, struct features_info_s **info, libcrun_error_t *err arg_unused) +{ + // Allocate memory for the features_info_s structure + size_t num_namspaces = sizeof (namespaces) / sizeof (namespaces[0]); + size_t num_operators = sizeof (operators) / sizeof (operators[0]); + size_t num_actions = sizeof (actions) / sizeof (actions[0]); + size_t num_hooks = sizeof (hooks) / sizeof (hooks[0]); + size_t num_archs = sizeof (archs) / sizeof (archs[0]); + size_t num_capabilities = 0; + char **capabilities = NULL; + + *info = xmalloc0 (sizeof (struct features_info_s)); + + // Hardcoded feature information + (*info)->oci_version_min = xstrdup ("1.0.0"); + (*info)->oci_version_max = xstrdup ("1.1.0-rc.3"); + + // Populate hooks + populate_array_field (&((*info)->hooks), hooks, num_hooks); + + // Populate mount_options + retrieve_mount_options (info); + + // Populate namespaces + populate_array_field (&((*info)->linux.namespaces), namespaces, num_namspaces); + + // Populate capabilities + populate_capabilities (*info, &capabilities, &num_capabilities); + + // Hardcode the values for cgroup + (*info)->linux.cgroup.v1 = true; + (*info)->linux.cgroup.v2 = true; +#ifdef HAVE_SYSTEMD + (*info)->linux.cgroup.systemd = true; + (*info)->linux.cgroup.systemd_user = true; +#endif + + // Put seccomp values +#ifdef HAVE_SECCOMP + (*info)->linux.seccomp.enabled = true; + // Populate actions + populate_array_field (&((*info)->linux.seccomp.actions), actions, num_actions); + // Populate operators + populate_array_field (&((*info)->linux.seccomp.operators), operators, num_operators); + // Populate archs + populate_array_field (&((*info)->linux.seccomp.archs), archs, num_archs); +#else + (*info)->linux.seccomp.enabled = false; +#endif + + // Put values for apparmor and selinux + (*info)->linux.apparmor.enabled = true; + (*info)->linux.selinux.enabled = true; + + // Populate the values for annotations +#ifdef HAVE_SECCOMP + { + const struct scmp_version *version = seccomp_version (); + int size = snprintf (NULL, 0, "%u.%u.%u", version->major, version->minor, version->micro) + 1; + char *version_string = xmalloc0 (size); + snprintf (version_string, size, "%u.%u.%u", version->major, version->minor, version->micro); + (*info)->annotations.io_github_seccomp_libseccomp_version = xstrdup (version_string); + } +#endif + + if (context->handler_manager && handler_by_name (context->handler_manager, "wasm")) + (*info)->annotations.run_oci_crun_wasm = true; + +#if HAVE_CRIU + (*info)->annotations.run_oci_crun_checkpoint_enabled = true; +#endif + (*info)->annotations.run_oci_crun_commit = GIT_VERSION; + (*info)->annotations.run_oci_crun_version = PACKAGE_VERSION; + + return 0; +} + int libcrun_container_spec (bool root, FILE *out, libcrun_error_t *err arg_unused) { diff --git a/src/libcrun/container.h b/src/libcrun/container.h index 9e4243e67..2511d0a0f 100644 --- a/src/libcrun/container.h +++ b/src/libcrun/container.h @@ -95,6 +95,60 @@ typedef struct libcrun_container_s libcrun_container_t; typedef struct libcrun_context_s libcrun_context_t; struct container_entrypoint_s; +struct cgroup_info_s +{ + bool v1; + bool v2; + bool systemd; + bool systemd_user; +}; + +struct seccomp_info_s +{ + bool enabled; + char **actions; + char **operators; + char **archs; +}; + +struct apparmor_info_s +{ + bool enabled; +}; + +struct selinux_info_s +{ + bool enabled; +}; + +struct linux_info_s +{ + char **namespaces; + char **capabilities; + struct cgroup_info_s cgroup; + struct seccomp_info_s seccomp; + struct apparmor_info_s apparmor; + struct selinux_info_s selinux; +}; + +struct annotations_info_s +{ + char *io_github_seccomp_libseccomp_version; + bool run_oci_crun_checkpoint_enabled; + char *run_oci_crun_commit; + char *run_oci_crun_version; + bool run_oci_crun_wasm; +}; + +struct features_info_s +{ + char *oci_version_min; + char *oci_version_max; + char **hooks; + char **mount_options; + struct linux_info_s linux; + struct annotations_info_s annotations; +}; struct libcrun_checkpoint_restore_s { @@ -184,6 +238,9 @@ LIBCRUN_PUBLIC int libcrun_container_update_from_values (libcrun_context_t *cont struct libcrun_update_value_s *values, size_t len, libcrun_error_t *err); +LIBCRUN_PUBLIC int libcrun_container_get_features (libcrun_context_t *context, struct features_info_s **info, + libcrun_error_t *err); + LIBCRUN_PUBLIC int libcrun_container_spec (bool root, FILE *out, libcrun_error_t *err); LIBCRUN_PUBLIC int libcrun_container_pause (libcrun_context_t *context, const char *id, libcrun_error_t *err); @@ -213,4 +270,69 @@ cleanup_containerp (libcrun_container_t **c) #define cleanup_container __attribute__ ((cleanup (cleanup_containerp))) +static inline void +cleanup_struct_features_free (struct features_info_s **info) +{ + size_t i; + struct features_info_s *ptr = *info; + if (info == NULL || *info == NULL) + return; + + // Free oci_version_min if it is not NULL + if (ptr->oci_version_min != NULL) + { + free (ptr->oci_version_min); + ptr->oci_version_min = NULL; // Set to NULL after freeing + } + + // Free oci_version_max if it is not NULL + if (ptr->oci_version_max != NULL) + { + free (ptr->oci_version_max); + ptr->oci_version_max = NULL; // Set to NULL after freeing + } + if (ptr->hooks != NULL) + { + for (i = 0; ptr->hooks[i] != NULL; i++) + free (ptr->hooks[i]); + free (ptr->hooks); + } + if (ptr->mount_options != NULL) + { + for (i = 0; ptr->mount_options[i] != NULL; i++) + free (ptr->mount_options[i]); + free (ptr->mount_options); + } + if (ptr->linux.namespaces != NULL) + { + for (i = 0; ptr->linux.namespaces[i] != NULL; i++) + free (ptr->linux.namespaces[i]); + free (ptr->linux.namespaces); + } + + if (ptr->linux.capabilities != NULL) + { + for (size_t i = 0; ptr->linux.capabilities[i] != NULL; i++) + free (ptr->linux.capabilities[i]); + free (ptr->linux.capabilities); + } + if (ptr->linux.seccomp.actions != NULL) + { + for (i = 0; ptr->linux.seccomp.actions[i] != NULL; i++) + free (ptr->linux.seccomp.actions[i]); + free (ptr->linux.seccomp.actions); + } + if (ptr->linux.seccomp.operators != NULL) + { + for (i = 0; ptr->linux.seccomp.operators[i] != NULL; i++) + free (ptr->linux.seccomp.operators[i]); + free (ptr->linux.seccomp.operators); + } + + free (ptr); + *info = NULL; +} + +#define cleanup_struct_features __attribute__ ((cleanup (cleanup_struct_features_free))) + #endif diff --git a/src/libcrun/mount_flags.c b/src/libcrun/mount_flags.c index 41296a3d9..83f8b96df 100644 --- a/src/libcrun/mount_flags.c +++ b/src/libcrun/mount_flags.c @@ -431,3 +431,18 @@ libcrun_str2mount_flags (const char *name) { return libcrun_mount_flag_in_word_set (name, strlen (name)); } + +const struct propagation_flags_s * +get_mount_flags_from_wordlist(void) { + struct propagation_flags_s *flags; + size_t i; + size_t num_wordlist_flags = sizeof(wordlist) / sizeof(wordlist[0]); + + flags = xmalloc0 ((sizeof(struct propagation_flags_s) + 1) * num_wordlist_flags); + + for (i = 0; i < num_wordlist_flags; i++) { + flags[i].name = wordlist[i].name; + } + + return flags; +} diff --git a/src/libcrun/mount_flags.h b/src/libcrun/mount_flags.h index 726960901..a93fce6cd 100644 --- a/src/libcrun/mount_flags.h +++ b/src/libcrun/mount_flags.h @@ -35,5 +35,6 @@ struct propagation_flags_s }; const struct propagation_flags_s *libcrun_str2mount_flags (const char *name); +const struct propagation_flags_s *get_mount_flags_from_wordlist (); #endif diff --git a/src/oci_features.c b/src/oci_features.c new file mode 100644 index 000000000..1283932e9 --- /dev/null +++ b/src/oci_features.c @@ -0,0 +1,250 @@ +/* + * crun - OCI runtime written in C + * + * crun is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * crun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with crun. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "crun.h" +#include "libcrun/container.h" +#include "libcrun/utils.h" + +static char doc[] = "OCI runtime"; + +static struct argp_option options[] = { { 0 } }; + +static char args_doc[] = "features"; + +const unsigned char *json_string; + +size_t json_length; + +static error_t +parse_opt (int key, char *arg arg_unused, struct argp_state *state arg_unused) +{ + if (key != ARGP_KEY_NO_ARGS) + { + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +static struct argp run_argp = { options, parse_opt, args_doc, doc, NULL, NULL, NULL }; + +void +add_string_to_json (yajl_gen json_gen, const char *key, char *value) +{ + yajl_gen_string (json_gen, (const unsigned char *) key, strlen (key)); + yajl_gen_string (json_gen, (const unsigned char *) value, strlen (value)); +} + +void +add_bool_to_json (yajl_gen json_gen, const char *key, int value) +{ + yajl_gen_string (json_gen, (const unsigned char *) key, strlen (key)); + yajl_gen_bool (json_gen, value); +} + +void +add_array_to_json (yajl_gen json_gen, const char *key, char **array) +{ + size_t i; + yajl_gen_string (json_gen, (const unsigned char *) key, strlen (key)); + yajl_gen_array_open (json_gen); + + for (i = 0; array[i] != NULL; i++) + yajl_gen_string (json_gen, (const unsigned char *) array[i], strlen (array[i])); + + yajl_gen_array_close (json_gen); +} + +void +crun_features_add_hooks (yajl_gen json_gen, char **hooks) +{ + add_array_to_json (json_gen, "hooks", hooks); +} + +void +crun_features_add_mount_options (yajl_gen json_gen, char **mount_options) +{ + add_array_to_json (json_gen, "mountOptions", mount_options); +} + +void +crun_features_add_namespaces (yajl_gen json_gen, const struct linux_info_s *linux) +{ + add_array_to_json (json_gen, "namespaces", linux->namespaces); +} + +void +crun_features_add_capabilities (yajl_gen json_gen, const struct linux_info_s *linux) +{ + add_array_to_json (json_gen, "capabilities", linux->capabilities); +} + +void +crun_features_add_cgroup_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "cgroup", strlen ("cgroup")); + yajl_gen_map_open (json_gen); + + add_bool_to_json (json_gen, "v1", linux->cgroup.v1); + add_bool_to_json (json_gen, "v2", linux->cgroup.v2); + add_bool_to_json (json_gen, "systemd", linux->cgroup.systemd); + add_bool_to_json (json_gen, "systemdUser", linux->cgroup.systemd_user); + + yajl_gen_map_close (json_gen); +} + +void +crun_features_add_seccomp_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "seccomp", strlen ("seccomp")); + yajl_gen_map_open (json_gen); + + add_bool_to_json (json_gen, "enabled", linux->seccomp.enabled); + add_array_to_json (json_gen, "actions", linux->seccomp.actions); + add_array_to_json (json_gen, "operators", linux->seccomp.operators); + + yajl_gen_map_close (json_gen); +} + +void +crun_features_add_apparmor_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "apparmor", strlen ("apparmor")); + yajl_gen_map_open (json_gen); + + add_bool_to_json (json_gen, "enabled", linux->apparmor.enabled); + + yajl_gen_map_close (json_gen); +} + +void +crun_features_add_selinux_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "selinux", strlen ("selinux")); + yajl_gen_map_open (json_gen); + + add_bool_to_json (json_gen, "enabled", linux->selinux.enabled); + + yajl_gen_map_close (json_gen); +} + +void +crun_features_add_linux_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "linux", strlen ("linux")); + yajl_gen_map_open (json_gen); + + crun_features_add_namespaces (json_gen, linux); + crun_features_add_capabilities (json_gen, linux); + crun_features_add_cgroup_info (json_gen, linux); + crun_features_add_seccomp_info (json_gen, linux); + crun_features_add_apparmor_info (json_gen, linux); + crun_features_add_selinux_info (json_gen, linux); + + yajl_gen_map_close (json_gen); +} + +void +crun_features_add_annotations_info (yajl_gen json_gen, const struct annotations_info_s *annotation) +{ + yajl_gen_string (json_gen, (const unsigned char *) "annotations", strlen ("annotations")); + yajl_gen_map_open (json_gen); + + add_string_to_json (json_gen, "io.github.seccomp.libseccomp.version", annotation->io_github_seccomp_libseccomp_version); + + add_bool_to_json (json_gen, "org.opencontainers.runc.checkpoint.enabled", annotation->run_oci_crun_checkpoint_enabled); + add_bool_to_json (json_gen, "run.oci.crun.checkpoint.enabled", annotation->run_oci_crun_checkpoint_enabled); + + add_string_to_json (json_gen, "run.oci.crun.commit", annotation->run_oci_crun_commit); + add_string_to_json (json_gen, "run.oci.crun.version", annotation->run_oci_crun_version); + + add_bool_to_json (json_gen, "run.oci.crun.wasm", annotation->run_oci_crun_wasm); + + yajl_gen_map_close (json_gen); +} + +int +crun_command_features (struct crun_global_arguments *global_args, int argc, char **argv, libcrun_error_t *err) +{ + cleanup_struct_features struct features_info_s *info = NULL; + int first_arg = 0, ret = 0; + libcrun_context_t crun_context = { + 0, + }; + yajl_gen json_gen; + + argp_parse (&run_argp, argc, argv, 0, 0, &options); + + ret = init_libcrun_context (&crun_context, argv[first_arg], global_args, err); + if (UNLIKELY (ret < 0)) + return ret; + + // Call the function in features.c to gather the feature information + ret = libcrun_container_get_features (&crun_context, &info, err); + if (UNLIKELY (ret < 0)) + return ret; + + // Prepare the JSON output + json_gen = yajl_gen_alloc (NULL); + if (json_gen == NULL) + return crun_make_error (err, errno, "Failed to initialize json structure"); + + yajl_gen_config (json_gen, yajl_gen_beautify, 1); // Optional: Enable pretty formatting + + // Start building the JSON + yajl_gen_map_open (json_gen); + + // Add ociVersionMin field + add_string_to_json (json_gen, "ociVersionMin", info->oci_version_min); + + // Add ociVersionMax field + add_string_to_json (json_gen, "ociVersionMax", info->oci_version_max); + + // Add hooks array + crun_features_add_hooks (json_gen, info->hooks); + + // Add mountOptions array + crun_features_add_mount_options (json_gen, info->mount_options); + + // Add linux struct info + crun_features_add_linux_info (json_gen, &info->linux); + + // Add annotations struct info + crun_features_add_annotations_info (json_gen, &info->annotations); + + // End building the JSON + yajl_gen_map_close (json_gen); + + yajl_gen_get_buf (json_gen, &json_string, &json_length); + + printf ("%s", (const char *) json_string); + + yajl_gen_free (json_gen); + + return 0; +} diff --git a/src/oci_features.h b/src/oci_features.h new file mode 100644 index 000000000..2a9d25e5c --- /dev/null +++ b/src/oci_features.h @@ -0,0 +1,25 @@ +/* + * crun - OCI runtime written in C + * + * Copyright (C) 2017, 2018, 2019 Giuseppe Scrivano + * crun is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * crun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with crun. If not, see . + */ +#ifndef OCI_FEATURES_H +#define OCI_FEATURES_H + +#include "crun.h" + +int crun_command_features (struct crun_global_arguments *global_args, int argc, char **argv, libcrun_error_t *error); + +#endif diff --git a/tests/test_oci_features.py b/tests/test_oci_features.py new file mode 100644 index 000000000..80d426714 --- /dev/null +++ b/tests/test_oci_features.py @@ -0,0 +1,225 @@ +#!/bin/env python3 +# crun - OCI runtime written in C +# +# crun is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# crun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with crun. If not, see . + +import subprocess +import json +import re +from tests_utils import * + +def is_systemd_enabled(): + return 'SYSTEMD' in get_crun_feature_string() + +def is_seccomp_enabled(): + return 'SECCOMP' in get_crun_feature_string() + +def get_crun_commit(): + try: + output = subprocess.check_output(["./crun", "--version"]).decode() + commit_match = re.search(r"commit: ([\w]+)", output) + + if commit_match: + return commit_match.group(1) + else: + raise ValueError("Commit information not found") + + except (subprocess.CalledProcessError, ValueError) as e: + print(f"Error retrieving crun commit: {str(e)}") + return None + +def test_crun_features(): + try: + output = run_crun_command(["features"]) + features = json.loads(output) + expected_features = { + "ociVersionMin": "1.0.0", + "ociVersionMax": "1.1.0-rc.3", + "hooks": [ + "prestart", + "createRuntime", + "createContainer", + "startContainer", + "poststart", + "poststop" + ], + "mountOptions": [ + "rw", + "rrw", + "ro", + "rro", + "rdirsync", + "rdiratime", + "rnodev", + "rnorelatime", + "nodiratime", + "rnodiratime", + "diratime", + "rnoatime", + "rnomand", + "ratime", + "rmand", + "mand", + "idmap", + "noatime", + "nomand", + "dirsync", + "rnosuid", + "atime", + "rnoexec", + "nodev", + "rbind", + "norelatime", + "bind", + "rnostrictatime", + "strictatime", + "rstrictatime", + "rprivate", + "rsuid", + "remount", + "suid", + "nostrictatime", + "rrelatime", + "nosuid", + "noexec", + "rslave", + "dev", + "rdev", + "rsync", + "relatime", + "sync", + "shared", + "rshared", + "unbindable", + "runbindable", + "defaults", + "async", + "rasync", + "private", + "tmpcopyup", + "rexec", + "exec", + "slave" + ], + "linux": { + "namespaces": [ + "cgroup", + "ipc", + "mount", + "network", + "pid", + "user", + "uts" + ], + "capabilities": [ + ], + "cgroup": { + "v1": True, + "v2": True, + }, + "seccomp": { + "actions": [ + "SCMP_ACT_ALLOW", + "SCMP_ACT_ERRNO", + "SCMP_ACT_KILL", + "SCMP_ACT_KILL_PROCESS", + "SCMP_ACT_KILL_THREAD", + "SCMP_ACT_LOG", + "SCMP_ACT_NOTIFY", + "SCMP_ACT_TRACE", + "SCMP_ACT_TRAP" + ], + "operators": [ + "SCMP_CMP_NE", + "SCMP_CMP_LT", + "SCMP_CMP_LE", + "SCMP_CMP_EQ", + "SCMP_CMP_GE", + "SCMP_CMP_GT", + "SCMP_CMP_MASKED_EQ" + ] + }, + "apparmor": { + "enabled": True + }, + "selinux": { + "enabled": True + } + }, + "annotations": { + "org.opencontainers.runc.checkpoint.enabled": True, + "run.oci.checkpoint.enabled": True, + "run.oci.commit": get_crun_commit(), + } + } + + systemd_enabled = is_systemd_enabled() + seccomp_enabled = is_seccomp_enabled() + + if seccomp_enabled: + expected_features["linux"]["seccomp"]["enabled"] = True + + # Check if systemd is enabled and set systemdUser accordingly + if systemd_enabled: + expected_features["linux"]["cgroup"]["systemd"] = True + expected_features["linux"]["cgroup"]["systemdUser"] = True + + for key, value in expected_features.items(): + if key == "linux" and isinstance(value, dict) and "capabilities" in value: + if "capabilities" in features.get("linux", {}): + capabilities = features["linux"]["capabilities"] + if not ("CAP_SYS_ADMIN" in capabilities and "CAP_KILL" in capabilities and "CAP_NET_BIND_SERVICE" in capabilities): + return -1 + continue + + if key == "annotations": + if "annotations" not in features: + sys.stderr.write("Annotations section is missing\n") + return -1 + + annotations = features["annotations"] + if annotations.get("run.oci.crun.commit") != get_crun_commit(): + sys.stderr.write("wrong value for run.oci.crun.commit\n") + return -1 + + if ('WASM' in get_crun_feature_string() + and annotations.get("run.oci.crun.wasm") is not True): + sys.stderr.write("wrong value for run.oci.crun.wasm\n") + return -1 + + if 'CRIU' in get_crun_feature_string(): + if annotations.get("org.opencontainers.runc.checkpoint.enabled") is not True: + sys.stderr.write("wrong value for org.opencontainers.runc.checkpoint.enabled\n") + return -1 + if annotations.get("run.oci.crun.checkpoint.enabled") is not True: + sys.stderr.write("wrong value for run.oci.crun.checkpoint.enabled\n") + return -1 + else: + if key not in features or features[key] != value: + sys.stderr.write(f"Mismatch in feature: {key}\n") + sys.stderr.write(f"Expected: {value}\n") + sys.stderr.write(f"Actual: {features.get(key)}\n") + return -1 + return 0 + + except Exception as e: + print("Error running crun features:", str(e)) + return -1 + +all_tests = { + "crun-features" : test_crun_features, +} + +if __name__ == "__main__": + tests_main(all_tests)