├── Makefile ├── .gitignore ├── go.mod ├── README.md ├── pkg ├── cleanup.go ├── images.go ├── mount.go ├── isolation.go ├── basic.go ├── systemd.go ├── networking.go ├── registries.go ├── registry_mirrors.go ├── pods.go └── security.go ├── LICENSE ├── main.go └── go.sum /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: 3 | go build . 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.conf 2 | *.json 3 | *.service 4 | *.yml 5 | Containerfile* 6 | deny-write 7 | fosdem20 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/saschagrunert/fosdem20 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/saschagrunert/demo v0.0.0-20200129143100-34782a2f88ab 7 | github.com/urfave/cli/v2 v2.1.1 8 | ) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Podman 2 | 3 | ### The Powerful Container Multi-Tool 4 | 5 | This repository contains all the resources of the FOSDEM 2020 talk about 6 | [Podman](https://podman.io). 7 | 8 | - [the slides](https://slides.com/saschagrunert/fosdem20) 9 | - [the talk](https://fosdem.org/2020/schedule/event/containers_podman) 10 | -------------------------------------------------------------------------------- /pkg/cleanup.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | . "github.com/saschagrunert/demo" 5 | "github.com/urfave/cli/v2" 6 | ) 7 | 8 | func Cleanup(ctx *cli.Context) error { 9 | _ = Ensure( 10 | "podman pod rm -fa", 11 | "podman rm -fa", 12 | "sudo podman pod rm -fa", 13 | "sudo podman rm -fa", 14 | ) 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /pkg/images.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | . "github.com/saschagrunert/demo" 5 | ) 6 | 7 | func Images() *Run { 8 | r := NewRun( 9 | "Images", 10 | "This demo shows how podman manages container images", 11 | ) 12 | 13 | r.Step(S( 14 | "Surely podman is able to build container images", 15 | ), S( 16 | "echo FROM scratch > Containerfile", 17 | )) 18 | r.Step(nil, S("podman build .")) 19 | 20 | r.Step(S( 21 | "Podman uses buildah under the hood to build containers.", 22 | "This means we can use mostly all features of buildah in", 23 | "podman as well, like the Containerfile preprocessor:", 24 | ), S( 25 | `cat < Containerfile.in 26 | FROM alpine 27 | #include "./Containerfile-clang" 28 | EOT`, 29 | )) 30 | 31 | r.Step(nil, S("echo RUN apk add clang > Containerfile-clang")) 32 | r.Step(nil, S("podman build -t clang -f Containerfile.in .")) 33 | 34 | r.Step(S( 35 | "Podman is now able to show an image tree, which", 36 | "contains useful image information", 37 | ), S( 38 | "podman image tree clang", 39 | )) 40 | 41 | return r 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sascha Grunert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/saschagrunert/demo" 5 | "github.com/saschagrunert/fosdem20/pkg" 6 | "github.com/urfave/cli/v2" 7 | ) 8 | 9 | func main() { 10 | d := New() 11 | d.Name = "fosdem20" 12 | d.HideVersion = true 13 | d.Usage = "Demo material used for the Podman talk at FOSDEM 2020" 14 | d.Authors = []*cli.Author{{ 15 | Name: "Sascha Grunert", Email: "sgrunert@suse.com", 16 | }} 17 | d.Setup(pkg.Cleanup) 18 | d.Cleanup(pkg.Cleanup) 19 | 20 | d.Add(pkg.Basic(), "basics", "run the basics of podman demo") 21 | d.Add(pkg.Isolation(), "isolation", "run the isolation demo") 22 | d.Add(pkg.Pods(), "pods", "run the pod management demo") 23 | d.Add(pkg.Security(), "security", "run the security demo") 24 | d.Add(pkg.Networking(), "networking", "run the networking demo") 25 | d.Add(pkg.Mount(), "mount", "run the mount container storage demo") 26 | d.Add(pkg.Images(), "images", "run the container images demo") 27 | d.Add(pkg.Systemd(), "systemd", "run the systemd demo") 28 | d.Add(pkg.Registries(), "registries", "run the registries demo") 29 | d.Add(pkg.RegistryMirrors(), "registry-mirror", "run the registry mirror demo") 30 | d.Run() 31 | } 32 | -------------------------------------------------------------------------------- /pkg/mount.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | . "github.com/saschagrunert/demo" 5 | ) 6 | 7 | func Mount() *Run { 8 | r := NewRun( 9 | "Mount", 10 | "This demo shows how to mount the container storage", 11 | "of a running container", 12 | ) 13 | 14 | r.Step(S( 15 | "Let’s create a container which tries to access a file", 16 | ), S( 17 | "sudo podman run --name cat-test -d alpine ", 18 | "sh -c 'while true; do cat test; sleep 2; done'", 19 | )) 20 | 21 | r.Step(S( 22 | "The file should not be available, which can be verified via the logs", 23 | ), S( 24 | "sudo podman logs -t cat-test", 25 | )) 26 | 27 | r.Step(S( 28 | "Now the containers file-system can be mounted", 29 | ), S( 30 | "sudo podman mount cat-test", 31 | )) 32 | 33 | r.Step(S( 34 | "And we can write the test file to it", 35 | ), S( 36 | "echo hello world | sudo tee -a $(sudo podman mount cat-test)/test", 37 | )) 38 | 39 | r.Step(S( 40 | "Now we can verify via the logs that the container has", 41 | "access to the file ", 42 | ), S( 43 | "sudo podman logs -t cat-test", 44 | )) 45 | 46 | r.Step(S( 47 | "Afterwards we can unmount the directory again", 48 | ), S( 49 | "sudo podman unmount cat-test", 50 | )) 51 | 52 | return r 53 | } 54 | -------------------------------------------------------------------------------- /pkg/isolation.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | . "github.com/saschagrunert/demo" 5 | ) 6 | 7 | func Isolation() *Run { 8 | r := NewRun( 9 | "Isolation", 10 | "This demo shows the isolation between containers and the host", 11 | ) 12 | 13 | r.Step(S( 14 | "With podman we have the chance to change the isolation between", 15 | "the running containers and their host.", 16 | "For example, we could remove the PID namespace isolation", 17 | ), S( 18 | "podman run --pid=host alpine ps aux | head -20", 19 | )) 20 | 21 | r.Step(S( 22 | "Or we could let two container share their namespace.", 23 | "For this we have to create a start container.", 24 | ), S( 25 | "podman run --name first -d alpine sleep infinity", 26 | )) 27 | 28 | r.Step(S( 29 | "Now we can start another container and specifying to join the PID", 30 | "namespace with the first one.", 31 | ), S( 32 | "podman run --pid container:first --name second -d alpine sleep infinity", 33 | )) 34 | 35 | r.Step(S( 36 | "We now have two running containers", 37 | ), S( 38 | "podman ps", 39 | )) 40 | 41 | r.Step(S( 42 | "But they share the same PID namespaces", 43 | ), S( 44 | "podman exec -it first ps aux", 45 | )) 46 | 47 | r.Step(S( 48 | "Podman is able to provide further namespace information", 49 | ), S( 50 | "podman ps --ns", 51 | )) 52 | 53 | return r 54 | } 55 | -------------------------------------------------------------------------------- /pkg/basic.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | . "github.com/saschagrunert/demo" 5 | ) 6 | 7 | func Basic() *Run { 8 | r := NewRun( 9 | "Basics", 10 | "The first demo demonstrates the basic usage of podman", 11 | ) 12 | 13 | r.Step(S( 14 | "Running a container in podman is a trivial task.", 15 | "Specified container images will be automatically downloaded", 16 | "in the same way as we would expect it from different", 17 | "container runtimes.", 18 | ), S( 19 | "podman run alpine echo hello world", 20 | )) 21 | 22 | r.Step(S( 23 | "We can utilize mainly all command line parameters we already", 24 | "know from docker", 25 | ), S( 26 | "podman run --name container -d alpine sleep infinity", 27 | )) 28 | 29 | r.Step(S( 30 | "Now the container should be running in the background", 31 | ), S( 32 | "podman ps", 33 | )) 34 | 35 | r.Step(S( 36 | "And we can exec that container, too", 37 | ), S( 38 | "podman exec container ps aux", 39 | )) 40 | 41 | r.Step(S( 42 | "There is no deamon running for podman, which means that some", 43 | "background process has to ensure the lifetime of the container.", 44 | "This is the job of the container monitoring tool `conmon`", 45 | ), S( 46 | "ps x | grep conmon", 47 | )) 48 | 49 | r.Step(S( 50 | "We can see that conmon interacts with runc to keep track of the", 51 | "containers lifecycle.", 52 | ), S( 53 | "runc list -f json | jq .", 54 | )) 55 | 56 | r.Step(S( 57 | "If we now kill the containers process, then conmon reports to podman", 58 | "that the container has exited", 59 | ), S( 60 | "pkill -9 sleep", 61 | )) 62 | 63 | r.Step(nil, S( 64 | "podman ps -a", 65 | )) 66 | 67 | return r 68 | } 69 | -------------------------------------------------------------------------------- /pkg/systemd.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | . "github.com/saschagrunert/demo" 5 | ) 6 | 7 | func Systemd() *Run { 8 | r := NewRun( 9 | "Systemd", 10 | "This demo shows how podman can generate systemd units", 11 | ) 12 | 13 | r.Step(S( 14 | "Podman is capable of generating systemd units for container", 15 | "workloads. For example if we start a new nginx server.", 16 | ), S( 17 | "podman run --name nginx -d nginx:alpine", 18 | )) 19 | 20 | r.Step(S( 21 | "Then we can use `generate systemd` to create the unit", 22 | ), S( 23 | "podman generate systemd -f -n nginx", 24 | )) 25 | r.Step(nil, S("cat container-nginx.service")) 26 | 27 | r.Step(S( 28 | "Now we can load the unit via systemd", 29 | ), S( 30 | "cp container-nginx.service ~/.config/systemd/user", 31 | )) 32 | r.Step(nil, S("systemctl enable --user container-nginx")) 33 | r.Step(nil, S("podman stop nginx")) 34 | r.Step(nil, S("podman ps")) 35 | r.Step(nil, S("systemctl start --user container-nginx")) 36 | r.Step(nil, S("podman ps")) 37 | 38 | r.Step(S( 39 | "It is also possible to run systemd inside a container with podman", 40 | "For this we have to prepare a container image", 41 | ), S( 42 | `cat < Containerfile 43 | FROM opensuse/tumbleweed 44 | RUN zypper in -y curl nginx systemd-sysvinit 45 | RUN systemctl enable nginx 46 | CMD [ "/usr/sbin/init" ] 47 | EOT`, 48 | )) 49 | r.Step(nil, S("podman build -t systemd .")) 50 | 51 | r.Step(S( 52 | "No we can run the nginx web server via systemd inside podman", 53 | ), S( 54 | "podman run --name systemd -d systemd", 55 | )) 56 | r.Step(nil, S("podman exec -it systemd ps aux")) 57 | r.Step(nil, S("podman exec -it systemd curl 127.0.0.1")) 58 | 59 | return r 60 | } 61 | -------------------------------------------------------------------------------- /pkg/networking.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | . "github.com/saschagrunert/demo" 5 | ) 6 | 7 | func Networking() *Run { 8 | r := NewRun( 9 | "Networking", 10 | "This demo shows how networking is operating in podman", 11 | ) 12 | 13 | r.StepCanFail(S( 14 | "Podman uses the Container Networking Interface (CNI) project", 15 | "to provide networking capablities", 16 | ), S( 17 | "sudo podman network ls", 18 | )) 19 | 20 | r.StepCanFail(S( 21 | "The configuration of the network plugins will be done in a system-wide", 22 | "directory, like /etc/cni/net.d", 23 | ), S( 24 | "jq . /etc/cni/net.d/87-podman-bridge.conflist", 25 | )) 26 | 27 | r.StepCanFail(S( 28 | "Podman chooses the default CNI network based on its configuration", 29 | "in /etc/containers/libpod.conf", 30 | ), S( 31 | "grep -B8 cni_default_network /etc/containers/libpod.conf", 32 | )) 33 | 34 | r.StepCanFail(S( 35 | "The CNI bridge plugin will ensure that we get the correct IP", 36 | "addresses assigned, let’s checkout how this looks like", 37 | ), S( 38 | "sudo podman run --name container -d alpine sleep infinity", 39 | )) 40 | r.StepCanFail(nil, S("sudo podman exec container ip addr")) 41 | r.StepCanFail(nil, S("ip netns")) 42 | r.StepCanFail(nil, S("ip link list type veth")) 43 | 44 | r.StepCanFail(S( 45 | "When running podman in rootless mode, then the project slirp4netns", 46 | "provides a user-mode networking via a TAP device.", 47 | ), S( 48 | "podman run alpine ip addr", 49 | )) 50 | 51 | r.StepCanFail(S( 52 | "It is also possible to run a container in host network mode", 53 | ), S( 54 | "podman run --net host alpine ip addr", 55 | )) 56 | 57 | r.StepCanFail(S( 58 | "Or join two network namespaces together… But for those use-cases we", 59 | "usually have pods, right?", 60 | ), nil) 61 | 62 | return r 63 | } 64 | -------------------------------------------------------------------------------- /pkg/registries.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | . "github.com/saschagrunert/demo" 7 | ) 8 | 9 | const r1 = `# An array of host[:port] registries to try when 10 | # pulling an unqualified image, in order. 11 | unqualified-search-registries = ["docker.io", "quay.io"] 12 | 13 | [[registry]] 14 | prefix = "localhost" 15 | location = "docker.io/library" 16 | blocked = false 17 | insecure = false 18 | ` 19 | 20 | const ( 21 | f = "registries.conf" 22 | k = "/etc/containers/" + f 23 | ) 24 | 25 | func Registries() *Run { 26 | r := NewRun( 27 | "Registry Configurations", 28 | "This demo shows how to configure registries with podman", 29 | ) 30 | 31 | r.Setup(func() error { 32 | if err := ioutil.WriteFile(f, []byte(r1), 0o644); err != nil { 33 | return err 34 | } 35 | 36 | return Ensure( 37 | "[ ! -f "+k+".bak ] && sudo mv "+k+" "+k+".bak || true", 38 | "sudo cp "+f+" "+k, 39 | "rm "+f, 40 | ) 41 | }) 42 | 43 | r.Step(S( 44 | "Podman supports multiple registry configuration syntaxes.", 45 | "From now on we focus on the latest version, which comes with the", 46 | "highest set of features. The default configuration can be found", 47 | "at "+k, 48 | ), S( 49 | "grep -B2 unqualified-search-registries "+k, 50 | )) 51 | 52 | r.Step(S( 53 | "The `unqualified-search-registries` allows us to pull images without", 54 | "prepending a registry prefix", 55 | ), S( 56 | "podman pull hello-world", 57 | )) 58 | 59 | r.Step(S( 60 | "A single registry can be specified within a [[registry]] entry", 61 | ), S( 62 | `grep -A4 '^\[\[registry\]\]' `+k, 63 | )) 64 | 65 | r.Step(S( 66 | "We have been rewritten the docker library to localhost.", 67 | "Now it is possible to pull via localhost", 68 | ), S( 69 | "podman --log-level=debug pull localhost/alpine 2>&1 | grep rewritten", 70 | )) 71 | 72 | return r 73 | } 74 | -------------------------------------------------------------------------------- /pkg/registry_mirrors.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | . "github.com/saschagrunert/demo" 7 | ) 8 | 9 | const r2 = `unqualified-search-registries = ["docker.io"] 10 | 11 | [[registry]] 12 | location = "docker.io/library" 13 | mirror = [ 14 | { location = "localhost/mirror-path" }, # doesnt work 15 | { location = "localhost:5000", insecure = true }, # should work 16 | ] 17 | ` 18 | 19 | func RegistryMirrors() *Run { 20 | r := NewRun( 21 | "Registry Mirrors", 22 | "This demo shows how to configure registries mirrors in podman", 23 | ) 24 | 25 | r.Setup(func() error { 26 | if err := ioutil.WriteFile(f, []byte(r2), 0o644); err != nil { 27 | return err 28 | } 29 | return Ensure( 30 | "[ ! -f "+k+".bak ] && sudo mv "+k+" "+k+".bak || true", 31 | "sudo cp "+f+" "+k, 32 | "rm "+f, 33 | ) 34 | }) 35 | 36 | r.Step(S( 37 | "Registry mirrors are especially useful in air-gapped scenarios,", 38 | "where access to the internet is limited.", 39 | "A registry mirror can be configured like this", 40 | ), S( 41 | `grep -A5 '^\[\[registry\]\]' `+k, 42 | )) 43 | 44 | r.Step(S( 45 | "To let the mirror work, we would have to setup one", 46 | "For this we use podman to setup a local registry", 47 | ), S( 48 | "podman run --rm --name=registry -p 5000:5000 -d registry", 49 | )) 50 | 51 | r.Step(S( 52 | "Now we can transfer our target image into the local registry", 53 | ), S( 54 | "podman pull hello-world", 55 | )) 56 | r.Step(nil, S( 57 | "podman tag hello-world localhost:5000/hello-world", 58 | )) 59 | r.Step(nil, S( 60 | "podman push --tls-verify=false localhost:5000/hello-world", 61 | )) 62 | 63 | r.Step(S( 64 | "If we now pull an image from docker.io, then we first lookup our", 65 | "configured mirrors.", 66 | ), S( 67 | "podman --log-level=debug pull hello-world 2>&1 | grep rewritten", 68 | )) 69 | 70 | return r 71 | } 72 | -------------------------------------------------------------------------------- /pkg/pods.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "os" 5 | 6 | . "github.com/saschagrunert/demo" 7 | ) 8 | 9 | func Pods() *Run { 10 | r := NewRun( 11 | "Pods", 12 | "This demo shows basic pod management in podman", 13 | ) 14 | 15 | r.Setup(func() error { 16 | _ = os.RemoveAll("pod.yml") 17 | return nil 18 | }) 19 | 20 | r.Step(S( 21 | "Pods are a concept from Kubernetes, where multiple containers", 22 | "share a set of common resources like the network interface.", 23 | "Podman supports pods as well. Let’s create one", 24 | ), S( 25 | "podman pod create --name my-pod", 26 | )) 27 | 28 | r.Step(S( 29 | "A new infra container has been created which is the base for the pod", 30 | ), S( 31 | "podman pod ps", 32 | )) 33 | 34 | r.Step(S( 35 | "Now it is possible to add a container to the pod", 36 | ), S( 37 | "podman run --pod my-pod -d alpine sleep infinity", 38 | )) 39 | 40 | r.Step(S( 41 | "Pods per default do not share the PID namespace.", 42 | "They share the network interface and IPC namespace,", 43 | "which makes it possible to communicate via the same localhost", 44 | ), S( 45 | "podman run --pod my-pod -d nginx:alpine", 46 | )) 47 | 48 | r.Step(S( 49 | "The webserver is now reachable via localhost, too", 50 | ), S( 51 | "podman run --pod my-pod alpine wget -q -O- 127.0.0.1", 52 | )) 53 | 54 | r.Step(S( 55 | "It is also possible to generate a Kubernetes manifest", 56 | "from the pod", 57 | ), S( 58 | "podman generate kube -f pod.yml my-pod", 59 | )) 60 | r.Step(nil, S("cat pod.yml")) 61 | 62 | r.Step(S( 63 | "The manifest can now be used to play around in Kubernetes.", 64 | "This works also the other way around. We can import the", 65 | "pod.yml back into podman as well. Let’s ensure that no workload", 66 | "is running", 67 | ), S( 68 | "podman pod rm -fa", 69 | )) 70 | r.Step(nil, S("podman ps -a")) 71 | 72 | r.Step(S( 73 | "Now we can utilize the `play kube` subcommand to re-create everything", 74 | "we deleted in the previous step", 75 | ), S( 76 | "podman play kube pod.yml", 77 | )) 78 | 79 | r.Step(S( 80 | "Everything should be now up and running again", 81 | ), S( 82 | "podman ps -a", 83 | )) 84 | 85 | return r 86 | } 87 | -------------------------------------------------------------------------------- /pkg/security.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | . "github.com/saschagrunert/demo" 5 | ) 6 | 7 | func Security() *Run { 8 | r := NewRun( 9 | "Security", 10 | "This demo shows the container security features of podman", 11 | ) 12 | 13 | r.StepCanFail(S( 14 | "Podman allows us to limit the Linux capabilities a container", 15 | "has. For example we could drop all of them", 16 | ), S( 17 | "podman run --cap-drop=all alpine ping 127.0.0.1", 18 | )) 19 | 20 | r.Step(S( 21 | "Or we add just the single capability we need", 22 | ), S( 23 | "podman run --cap-drop=all --cap-add=net_raw alpine ping -c3 127.0.0.1", 24 | )) 25 | 26 | r.Step(S( 27 | "We could also use SECCOMP to limit the container in a more", 28 | "powerful way. For that we have to define a SECCOMP profile JSON", 29 | ), S( 30 | `cat < profile.json 31 | { 32 | "defaultAction": "SCMP_ACT_ALLOW", 33 | "architectures": ["SCMP_ARCH_X86_64"], 34 | "syscalls": [ { "names": ["mkdir"], "action": "SCMP_ACT_ERRNO" } ] 35 | } 36 | EOT`, 37 | )) 38 | 39 | r.StepCanFail(S( 40 | "Now podman can utilize the profile to limit the `mkdir(2)` syscall", 41 | ), S( 42 | "podman run --security-opt seccomp=profile.json alpine mkdir test", 43 | )) 44 | 45 | r.Step(S( 46 | "Podman supports AppArmor and SELinux as well.", 47 | "For example if we load a simple 'deny-write' AppArmor profile", 48 | ), S( 49 | `cat < deny-write 50 | #include 51 | 52 | profile deny-write flags=(attach_disconnected) { 53 | #include 54 | 55 | file, 56 | 57 | # Deny all file writes. 58 | deny /** w, 59 | } 60 | EOT`, 61 | )) 62 | 63 | r.Step(S( 64 | "The profile has to be loaded before running podman", 65 | ), S( 66 | "sudo apparmor_parser -r deny-write", 67 | )) 68 | 69 | r.StepCanFail(S( 70 | "Now podman can use the profile in the same way as for SECCOMP", 71 | ), S( 72 | "sudo podman run --security-opt apparmor=deny-write alpine mkdir test", 73 | )) 74 | 75 | r.Step(S( 76 | "It is always recommended to run container based workloads as non root.", 77 | "This reduces the possible attack surface. It’s fairly easy to switch", 78 | "to a different user in podman via the `--user` flag.", 79 | ), S( 80 | "podman run --user guest alpine whoami", 81 | )) 82 | 83 | r.StepCanFail(S( 84 | "This user should not be able to modify the root file system of the container", 85 | ), S( 86 | "podman run --user guest alpine touch test", 87 | )) 88 | 89 | return r 90 | } 91 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 6 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 7 | github.com/gookit/color v1.2.0 h1:lHA77Kuyi5JpBnA9ESvwkY+nanLjRZ0mHbWQXRYk2Lk= 8 | github.com/gookit/color v1.2.0/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= 9 | github.com/gookit/color v1.2.2 h1:IPG03BHqn23rgU597fFC8UNdlvEbQrZxYQyZqE0wkDw= 10 | github.com/gookit/color v1.2.2/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= 11 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 12 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 13 | github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 14 | github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= 15 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 16 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 17 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 18 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 21 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 22 | github.com/saschagrunert/crio-demos v0.0.0-20191120210543-1c01c4b3bd05 h1:IiGNcW8fuaaNRn/9sebrLCc/oL+KfdqDkNmvt7ZudqI= 23 | github.com/saschagrunert/crio-demos v0.0.0-20191120210543-1c01c4b3bd05/go.mod h1:0U8TomUawy40lNicXLgMjN0kD2yr/tGUTzQbx3Tk1Mw= 24 | github.com/saschagrunert/demo v0.0.0-20191230171347-c3008e644bb6 h1:FAcQ9bkNmVHrRXPxDIt6FegUI9UjIgKUApv1tXnKnBw= 25 | github.com/saschagrunert/demo v0.0.0-20191230171347-c3008e644bb6/go.mod h1:b8fh/ImMVYhCNYXPmhCU2DvdCVFH8/TDLm10SF/O0sQ= 26 | github.com/saschagrunert/demo v0.0.0-20200123090403-f6a1b7426328 h1:knFOkf0aA+fvTywDqmDiZwwHB4BZUENrTudr7jSsGP8= 27 | github.com/saschagrunert/demo v0.0.0-20200123090403-f6a1b7426328/go.mod h1:PA1FuAq0mgayVJRmeLB1RpQfgS2ZBnnNUYo8jC9tlqI= 28 | github.com/saschagrunert/demo v0.0.0-20200123111554-3fd993247ddd h1:juR0pFkg6grYpcP2bF1uvGxxt4H6tRftYeUGGv9jITk= 29 | github.com/saschagrunert/demo v0.0.0-20200123111554-3fd993247ddd/go.mod h1:PA1FuAq0mgayVJRmeLB1RpQfgS2ZBnnNUYo8jC9tlqI= 30 | github.com/saschagrunert/demo v0.0.0-20200123150837-965497916bdb h1:J8k9PePNl6ze4MZ/xFqdynQGKFlNDtZhlaSNXs32RQo= 31 | github.com/saschagrunert/demo v0.0.0-20200123150837-965497916bdb/go.mod h1:PA1FuAq0mgayVJRmeLB1RpQfgS2ZBnnNUYo8jC9tlqI= 32 | github.com/saschagrunert/demo v0.0.0-20200129143100-34782a2f88ab h1:BXM5dR+RCb9c0jM6oTe+jlQTnAVzofMbj1IrBmCpbpA= 33 | github.com/saschagrunert/demo v0.0.0-20200129143100-34782a2f88ab/go.mod h1:PA1FuAq0mgayVJRmeLB1RpQfgS2ZBnnNUYo8jC9tlqI= 34 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 35 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 36 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 37 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 38 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 39 | github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= 40 | github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 41 | github.com/urfave/cli/v2 v2.0.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= 42 | github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= 43 | github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= 44 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 45 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 46 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 49 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 50 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 51 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 52 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 53 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 54 | --------------------------------------------------------------------------------