1use std::collections::HashMap;
2
3use crate::{
4    error::OciSpecError,
5    runtime::{Arch, LinuxNamespaceType, LinuxSeccompAction},
6};
7use derive_builder::Builder;
8use getset::{Getters, MutGetters, Setters};
9use serde::{Deserialize, Serialize};
10
11#[derive(
16    Builder,
17    Clone,
18    Debug,
19    Default,
20    Deserialize,
21    Eq,
22    MutGetters,
23    Getters,
24    Setters,
25    PartialEq,
26    Serialize,
27)]
28#[serde(rename_all = "camelCase")]
29#[builder(
30    default,
31    pattern = "owned",
32    setter(into, strip_option),
33    build_fn(error = "OciSpecError")
34)]
35#[getset(get_mut = "pub", get = "pub", set = "pub")]
36pub struct Features {
37    oci_version_min: String,
39    oci_version_max: String,
41    hooks: Option<Vec<String>>,
44    mount_options: Option<Vec<String>>,
48    linux: Option<LinuxFeature>,
50    annotations: Option<HashMap<String, String>>,
53    potentially_unsafe_config_annotations: Option<Vec<String>>,
57}
58
59#[derive(
61    Builder,
62    Clone,
63    Debug,
64    Default,
65    Deserialize,
66    Eq,
67    MutGetters,
68    Getters,
69    Setters,
70    PartialEq,
71    Serialize,
72)]
73#[serde(rename_all = "camelCase")]
74#[builder(
75    default,
76    pattern = "owned",
77    setter(into, strip_option),
78    build_fn(error = "OciSpecError")
79)]
80#[getset(get_mut = "pub", get = "pub", set = "pub")]
81pub struct LinuxFeature {
82    namespaces: Option<Vec<LinuxNamespaceType>>,
85    capabilities: Option<Vec<String>>,
88    cgroup: Option<Cgroup>,
90    seccomp: Option<Seccomp>,
92    apparmor: Option<Apparmor>,
94    selinux: Option<Selinux>,
96    intel_rdt: Option<IntelRdt>,
98    mount_extensions: Option<MountExtensions>,
100}
101
102#[derive(
104    Builder,
105    Clone,
106    Debug,
107    Default,
108    Deserialize,
109    Eq,
110    MutGetters,
111    Getters,
112    Setters,
113    PartialEq,
114    Serialize,
115)]
116#[serde(rename_all = "camelCase")]
117#[builder(
118    default,
119    pattern = "owned",
120    setter(into, strip_option),
121    build_fn(error = "OciSpecError")
122)]
123#[getset(get_mut = "pub", get = "pub", set = "pub")]
124pub struct Cgroup {
125    v1: Option<bool>,
129    v2: Option<bool>,
133    systemd: Option<bool>,
137    systemd_user: Option<bool>,
141    rdma: Option<bool>,
145}
146
147#[derive(
149    Builder,
150    Clone,
151    Debug,
152    Default,
153    Deserialize,
154    Eq,
155    MutGetters,
156    Getters,
157    Setters,
158    PartialEq,
159    Serialize,
160)]
161#[serde(rename_all = "camelCase")]
162#[builder(
163    default,
164    pattern = "owned",
165    setter(into, strip_option),
166    build_fn(error = "OciSpecError")
167)]
168#[getset(get_mut = "pub", get = "pub", set = "pub")]
169pub struct Seccomp {
170    enabled: Option<bool>,
173    actions: Option<Vec<LinuxSeccompAction>>,
176    operators: Option<Vec<String>>,
179    archs: Option<Vec<Arch>>,
182    known_flags: Option<Vec<String>>,
185    supported_flags: Option<Vec<String>>,
190}
191
192#[derive(
194    Builder,
195    Clone,
196    Debug,
197    Default,
198    Deserialize,
199    Eq,
200    MutGetters,
201    Getters,
202    Setters,
203    PartialEq,
204    Serialize,
205)]
206#[serde(rename_all = "camelCase")]
207#[builder(
208    default,
209    pattern = "owned",
210    setter(into, strip_option),
211    build_fn(error = "OciSpecError")
212)]
213#[getset(get_mut = "pub", get = "pub", set = "pub")]
214pub struct Apparmor {
215    enabled: Option<bool>,
219}
220
221#[derive(
223    Builder,
224    Clone,
225    Debug,
226    Default,
227    Deserialize,
228    Eq,
229    MutGetters,
230    Getters,
231    Setters,
232    PartialEq,
233    Serialize,
234)]
235#[serde(rename_all = "camelCase")]
236#[builder(
237    default,
238    pattern = "owned",
239    setter(into, strip_option),
240    build_fn(error = "OciSpecError")
241)]
242#[getset(get_mut = "pub", get = "pub", set = "pub")]
243pub struct Selinux {
244    enabled: Option<bool>,
248}
249
250#[derive(
252    Builder,
253    Clone,
254    Debug,
255    Default,
256    Deserialize,
257    Eq,
258    MutGetters,
259    Getters,
260    Setters,
261    PartialEq,
262    Serialize,
263)]
264#[serde(rename_all = "camelCase")]
265#[builder(
266    default,
267    pattern = "owned",
268    setter(into, strip_option),
269    build_fn(error = "OciSpecError")
270)]
271#[getset(get_mut = "pub", get = "pub", set = "pub")]
272pub struct IntelRdt {
273    enabled: Option<bool>,
276}
277
278#[derive(
280    Builder,
281    Clone,
282    Debug,
283    Default,
284    Deserialize,
285    Eq,
286    MutGetters,
287    Getters,
288    Setters,
289    PartialEq,
290    Serialize,
291)]
292#[serde(rename_all = "camelCase")]
293#[builder(
294    default,
295    pattern = "owned",
296    setter(into, strip_option),
297    build_fn(error = "OciSpecError")
298)]
299#[getset(get_mut = "pub", get = "pub", set = "pub")]
300pub struct MountExtensions {
301    idmap: Option<IDMap>,
303}
304
305#[derive(
307    Builder,
308    Clone,
309    Debug,
310    Default,
311    Deserialize,
312    Eq,
313    MutGetters,
314    Getters,
315    Setters,
316    PartialEq,
317    Serialize,
318)]
319#[serde(rename_all = "camelCase")]
320#[builder(
321    default,
322    pattern = "owned",
323    setter(into, strip_option),
324    build_fn(error = "OciSpecError")
325)]
326#[getset(get_mut = "pub", get = "pub", set = "pub")]
327pub struct IDMap {
328    enabled: Option<bool>,
332}
333
334#[cfg(test)]
335mod tests {
336    use std::ops::Deref;
337
338    use super::*;
339
340    #[test]
341    fn test_parse_features() {
342        let example_json = r#"
343{
344    "ociVersionMin": "1.0.0",
345    "ociVersionMax": "1.1.0-rc.2",
346    "hooks": [
347        "prestart",
348        "createRuntime",
349        "createContainer",
350        "startContainer",
351        "poststart",
352        "poststop"
353    ],
354    "mountOptions": [
355        "async",
356        "atime",
357        "bind",
358        "defaults",
359        "dev",
360        "diratime",
361        "dirsync",
362        "exec",
363        "iversion",
364        "lazytime",
365        "loud",
366        "mand",
367        "noatime",
368        "nodev",
369        "nodiratime",
370        "noexec",
371        "noiversion",
372        "nolazytime",
373        "nomand",
374        "norelatime",
375        "nostrictatime",
376        "nosuid",
377        "nosymfollow",
378        "private",
379        "ratime",
380        "rbind",
381        "rdev",
382        "rdiratime",
383        "relatime",
384        "remount",
385        "rexec",
386        "rnoatime",
387        "rnodev",
388        "rnodiratime",
389        "rnoexec",
390        "rnorelatime",
391        "rnostrictatime",
392        "rnosuid",
393        "rnosymfollow",
394        "ro",
395        "rprivate",
396        "rrelatime",
397        "rro",
398        "rrw",
399        "rshared",
400        "rslave",
401        "rstrictatime",
402        "rsuid",
403        "rsymfollow",
404        "runbindable",
405        "rw",
406        "shared",
407        "silent",
408        "slave",
409        "strictatime",
410        "suid",
411        "symfollow",
412        "sync",
413        "tmpcopyup",
414        "unbindable"
415    ],
416    "linux": {
417        "namespaces": [
418            "cgroup",
419            "ipc",
420            "mount",
421            "network",
422            "pid",
423            "user",
424            "uts"
425        ],
426        "capabilities": [
427            "CAP_CHOWN",
428            "CAP_DAC_OVERRIDE",
429            "CAP_DAC_READ_SEARCH",
430            "CAP_FOWNER",
431            "CAP_FSETID",
432            "CAP_KILL",
433            "CAP_SETGID",
434            "CAP_SETUID",
435            "CAP_SETPCAP",
436            "CAP_LINUX_IMMUTABLE",
437            "CAP_NET_BIND_SERVICE",
438            "CAP_NET_BROADCAST",
439            "CAP_NET_ADMIN",
440            "CAP_NET_RAW",
441            "CAP_IPC_LOCK",
442            "CAP_IPC_OWNER",
443            "CAP_SYS_MODULE",
444            "CAP_SYS_RAWIO",
445            "CAP_SYS_CHROOT",
446            "CAP_SYS_PTRACE",
447            "CAP_SYS_PACCT",
448            "CAP_SYS_ADMIN",
449            "CAP_SYS_BOOT",
450            "CAP_SYS_NICE",
451            "CAP_SYS_RESOURCE",
452            "CAP_SYS_TIME",
453            "CAP_SYS_TTY_CONFIG",
454            "CAP_MKNOD",
455            "CAP_LEASE",
456            "CAP_AUDIT_WRITE",
457            "CAP_AUDIT_CONTROL",
458            "CAP_SETFCAP",
459            "CAP_MAC_OVERRIDE",
460            "CAP_MAC_ADMIN",
461            "CAP_SYSLOG",
462            "CAP_WAKE_ALARM",
463            "CAP_BLOCK_SUSPEND",
464            "CAP_AUDIT_READ",
465            "CAP_PERFMON",
466            "CAP_BPF",
467            "CAP_CHECKPOINT_RESTORE"
468        ],
469        "cgroup": {
470            "v1": true,
471            "v2": true,
472            "systemd": true,
473            "systemdUser": true,
474            "rdma": true
475        },
476        "seccomp": {
477            "enabled": true,
478            "actions": [
479                "SCMP_ACT_ALLOW",
480                "SCMP_ACT_ERRNO",
481                "SCMP_ACT_KILL",
482                "SCMP_ACT_KILL_PROCESS",
483                "SCMP_ACT_KILL_THREAD",
484                "SCMP_ACT_LOG",
485                "SCMP_ACT_NOTIFY",
486                "SCMP_ACT_TRACE",
487                "SCMP_ACT_TRAP"
488            ],
489            "operators": [
490                "SCMP_CMP_EQ",
491                "SCMP_CMP_GE",
492                "SCMP_CMP_GT",
493                "SCMP_CMP_LE",
494                "SCMP_CMP_LT",
495                "SCMP_CMP_MASKED_EQ",
496                "SCMP_CMP_NE"
497            ],
498            "archs": [
499                "SCMP_ARCH_AARCH64",
500                "SCMP_ARCH_ARM",
501                "SCMP_ARCH_MIPS",
502                "SCMP_ARCH_MIPS64",
503                "SCMP_ARCH_MIPS64N32",
504                "SCMP_ARCH_MIPSEL",
505                "SCMP_ARCH_MIPSEL64",
506                "SCMP_ARCH_MIPSEL64N32",
507                "SCMP_ARCH_PPC",
508                "SCMP_ARCH_PPC64",
509                "SCMP_ARCH_PPC64LE",
510                "SCMP_ARCH_RISCV64",
511                "SCMP_ARCH_S390",
512                "SCMP_ARCH_S390X",
513                "SCMP_ARCH_X32",
514                "SCMP_ARCH_X86",
515                "SCMP_ARCH_X86_64"
516            ],
517            "knownFlags": [
518                "SECCOMP_FILTER_FLAG_TSYNC",
519                "SECCOMP_FILTER_FLAG_SPEC_ALLOW",
520                "SECCOMP_FILTER_FLAG_LOG"
521            ],
522            "supportedFlags": [
523                "SECCOMP_FILTER_FLAG_TSYNC",
524                "SECCOMP_FILTER_FLAG_SPEC_ALLOW",
525                "SECCOMP_FILTER_FLAG_LOG"
526            ]
527        },
528        "apparmor": {
529            "enabled": true
530        },
531        "selinux": {
532            "enabled": true
533        },
534        "intelRdt": {
535            "enabled": true
536        }
537    },
538    "annotations": {
539        "io.github.seccomp.libseccomp.version": "2.5.4",
540        "org.opencontainers.runc.checkpoint.enabled": "true",
541        "org.opencontainers.runc.commit": "v1.1.0-534-g26851168",
542        "org.opencontainers.runc.version": "1.1.0+dev"
543    }
544}"#;
545
546        let features: Features = serde_json::from_str(example_json).unwrap();
548        assert_eq!(features.oci_version_min().deref(), "1.0.0".to_string());
549        assert_eq!(features.oci_version_max().deref(), "1.1.0-rc.2".to_string());
550
551        assert_eq!(
552            features.hooks.as_ref().unwrap(),
553            &[
554                "prestart",
555                "createRuntime",
556                "createContainer",
557                "startContainer",
558                "poststart",
559                "poststop"
560            ]
561        );
562
563        assert_eq!(
564            features.mount_options.as_ref().unwrap(),
565            &[
566                "async",
567                "atime",
568                "bind",
569                "defaults",
570                "dev",
571                "diratime",
572                "dirsync",
573                "exec",
574                "iversion",
575                "lazytime",
576                "loud",
577                "mand",
578                "noatime",
579                "nodev",
580                "nodiratime",
581                "noexec",
582                "noiversion",
583                "nolazytime",
584                "nomand",
585                "norelatime",
586                "nostrictatime",
587                "nosuid",
588                "nosymfollow",
589                "private",
590                "ratime",
591                "rbind",
592                "rdev",
593                "rdiratime",
594                "relatime",
595                "remount",
596                "rexec",
597                "rnoatime",
598                "rnodev",
599                "rnodiratime",
600                "rnoexec",
601                "rnorelatime",
602                "rnostrictatime",
603                "rnosuid",
604                "rnosymfollow",
605                "ro",
606                "rprivate",
607                "rrelatime",
608                "rro",
609                "rrw",
610                "rshared",
611                "rslave",
612                "rstrictatime",
613                "rsuid",
614                "rsymfollow",
615                "runbindable",
616                "rw",
617                "shared",
618                "silent",
619                "slave",
620                "strictatime",
621                "suid",
622                "symfollow",
623                "sync",
624                "tmpcopyup",
625                "unbindable"
626            ]
627        );
628
629        let linux = features.linux().as_ref().unwrap();
630
631        assert_eq!(
632            linux.namespaces.as_ref().unwrap(),
633            &[
634                LinuxNamespaceType::Cgroup,
635                LinuxNamespaceType::Ipc,
636                LinuxNamespaceType::Mount,
637                LinuxNamespaceType::Network,
638                LinuxNamespaceType::Pid,
639                LinuxNamespaceType::User,
640                LinuxNamespaceType::Uts,
641            ]
642        );
643
644        assert_eq!(
645            linux.capabilities.as_ref().unwrap(),
646            &[
647                "CAP_CHOWN",
648                "CAP_DAC_OVERRIDE",
649                "CAP_DAC_READ_SEARCH",
650                "CAP_FOWNER",
651                "CAP_FSETID",
652                "CAP_KILL",
653                "CAP_SETGID",
654                "CAP_SETUID",
655                "CAP_SETPCAP",
656                "CAP_LINUX_IMMUTABLE",
657                "CAP_NET_BIND_SERVICE",
658                "CAP_NET_BROADCAST",
659                "CAP_NET_ADMIN",
660                "CAP_NET_RAW",
661                "CAP_IPC_LOCK",
662                "CAP_IPC_OWNER",
663                "CAP_SYS_MODULE",
664                "CAP_SYS_RAWIO",
665                "CAP_SYS_CHROOT",
666                "CAP_SYS_PTRACE",
667                "CAP_SYS_PACCT",
668                "CAP_SYS_ADMIN",
669                "CAP_SYS_BOOT",
670                "CAP_SYS_NICE",
671                "CAP_SYS_RESOURCE",
672                "CAP_SYS_TIME",
673                "CAP_SYS_TTY_CONFIG",
674                "CAP_MKNOD",
675                "CAP_LEASE",
676                "CAP_AUDIT_WRITE",
677                "CAP_AUDIT_CONTROL",
678                "CAP_SETFCAP",
679                "CAP_MAC_OVERRIDE",
680                "CAP_MAC_ADMIN",
681                "CAP_SYSLOG",
682                "CAP_WAKE_ALARM",
683                "CAP_BLOCK_SUSPEND",
684                "CAP_AUDIT_READ",
685                "CAP_PERFMON",
686                "CAP_BPF",
687                "CAP_CHECKPOINT_RESTORE"
688            ],
689        );
690
691        assert_eq!(
692            linux.cgroup.as_ref().unwrap(),
693            &Cgroup {
694                v1: Some(true),
695                v2: Some(true),
696                systemd: Some(true),
697                systemd_user: Some(true),
698                rdma: Some(true),
699            }
700        );
701
702        assert_eq!(
703            linux.seccomp.as_ref().unwrap(),
704            &Seccomp {
705                enabled: Some(true),
706                actions: Some(vec![
707                    LinuxSeccompAction::ScmpActAllow,
708                    LinuxSeccompAction::ScmpActErrno,
709                    LinuxSeccompAction::ScmpActKill,
710                    LinuxSeccompAction::ScmpActKillProcess,
711                    LinuxSeccompAction::ScmpActKillThread,
712                    LinuxSeccompAction::ScmpActLog,
713                    LinuxSeccompAction::ScmpActNotify,
714                    LinuxSeccompAction::ScmpActTrace,
715                    LinuxSeccompAction::ScmpActTrap
716                ]),
717                operators: Some(vec![
718                    "SCMP_CMP_EQ".to_string(),
719                    "SCMP_CMP_GE".to_string(),
720                    "SCMP_CMP_GT".to_string(),
721                    "SCMP_CMP_LE".to_string(),
722                    "SCMP_CMP_LT".to_string(),
723                    "SCMP_CMP_MASKED_EQ".to_string(),
724                    "SCMP_CMP_NE".to_string()
725                ]),
726                archs: Some(vec![
727                    Arch::ScmpArchAarch64,
728                    Arch::ScmpArchArm,
729                    Arch::ScmpArchMips,
730                    Arch::ScmpArchMips64,
731                    Arch::ScmpArchMips64n32,
732                    Arch::ScmpArchMipsel,
733                    Arch::ScmpArchMipsel64,
734                    Arch::ScmpArchMipsel64n32,
735                    Arch::ScmpArchPpc,
736                    Arch::ScmpArchPpc64,
737                    Arch::ScmpArchPpc64le,
738                    Arch::ScmpArchRiscv64,
739                    Arch::ScmpArchS390,
740                    Arch::ScmpArchS390x,
741                    Arch::ScmpArchX32,
742                    Arch::ScmpArchX86,
743                    Arch::ScmpArchX86_64,
744                ]),
745                known_flags: Some(vec![
746                    "SECCOMP_FILTER_FLAG_TSYNC".to_string(),
747                    "SECCOMP_FILTER_FLAG_SPEC_ALLOW".to_string(),
748                    "SECCOMP_FILTER_FLAG_LOG".to_string()
749                ]),
750                supported_flags: Some(vec![
751                    "SECCOMP_FILTER_FLAG_TSYNC".to_string(),
752                    "SECCOMP_FILTER_FLAG_SPEC_ALLOW".to_string(),
753                    "SECCOMP_FILTER_FLAG_LOG".to_string()
754                ])
755            },
756        );
757
758        assert_eq!(
759            linux.apparmor.as_ref().unwrap(),
760            &Apparmor {
761                enabled: Some(true)
762            }
763        );
764
765        assert_eq!(
766            linux.selinux.as_ref().unwrap(),
767            &Selinux {
768                enabled: Some(true)
769            }
770        );
771
772        assert_eq!(
773            linux.intel_rdt.as_ref().unwrap(),
774            &IntelRdt {
775                enabled: Some(true)
776            }
777        );
778
779        assert_eq!(
780            features.annotations().as_ref().unwrap(),
781            &[
782                (
783                    "io.github.seccomp.libseccomp.version".to_string(),
784                    "2.5.4".to_string()
785                ),
786                (
787                    "org.opencontainers.runc.checkpoint.enabled".to_string(),
788                    "true".to_string()
789                ),
790                (
791                    "org.opencontainers.runc.commit".to_string(),
792                    "v1.1.0-534-g26851168".to_string()
793                ),
794                (
795                    "org.opencontainers.runc.version".to_string(),
796                    "1.1.0+dev".to_string()
797                )
798            ]
799            .iter()
800            .cloned()
801            .collect()
802        );
803
804        assert_eq!(
805            features.potentially_unsafe_config_annotations().as_ref(),
806            None,
807        );
808    }
809}