diff options
-rw-r--r-- | .cirrus.yml | 26 | ||||
-rwxr-xr-x | API.md | 24 | ||||
-rw-r--r-- | CONTRIBUTING.md | 4 | ||||
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | cmd/podman/create.go | 63 | ||||
-rw-r--r-- | cmd/podman/generate_kube.go | 1 | ||||
-rw-r--r-- | cmd/podman/image.go | 1 | ||||
-rw-r--r-- | cmd/podman/main.go | 1 | ||||
-rw-r--r-- | cmd/podman/play.go | 23 | ||||
-rw-r--r-- | cmd/podman/play_kube.go | 245 | ||||
-rw-r--r-- | cmd/podman/trust.go | 293 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 12 | ||||
-rw-r--r-- | completions/bash/podman | 37 | ||||
-rw-r--r-- | contrib/gate/Dockerfile | 2 | ||||
-rw-r--r-- | docs/podman-generate-kube.1.md | 2 | ||||
-rw-r--r-- | docs/podman-image-trust.1.md | 81 | ||||
-rw-r--r-- | docs/podman-image.1.md | 1 | ||||
-rw-r--r-- | docs/podman-play-kube.1.md | 78 | ||||
-rw-r--r-- | docs/podman-play.1.md | 20 | ||||
-rw-r--r-- | docs/tutorials/podman_tutorial.md | 6 | ||||
-rw-r--r-- | libpod/image/utils.go | 22 | ||||
-rw-r--r-- | libpod/kube.go | 34 | ||||
-rw-r--r-- | libpod/runtime.go | 5 | ||||
-rw-r--r-- | pkg/trust/trust.go | 250 | ||||
-rw-r--r-- | test/e2e/trust_test.go | 72 | ||||
-rw-r--r-- | test/install/Dockerfile.Fedora | 4 |
26 files changed, 1267 insertions, 45 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index f18e82653..7afd8f0b3 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -22,6 +22,7 @@ env: # Save a little typing (path relative to $CIRRUS_WORKING_DIR) SCRIPT_BASE: "./contrib/cirrus" PACKER_BASE: "./contrib/cirrus/packer" + CIRRUS_CLONE_DEPTH: 200 #### #### Variables for composing new cache-images (used in PR testing) from @@ -97,11 +98,36 @@ gating_task: - '/usr/local/bin/entrypoint.sh lint' +build_each_commit_task: + + depends_on: + - "gating" + + # $CIRRUS_BASE_BRANCH is only set when testing a PR + only_if: $CIRRUS_BRANCH != 'master' + + gce_instance: + image_project: "libpod-218412" + zone: "us-central1-a" # Required by Cirrus for the time being + cpu: 2 + memory: "4Gb" + disk: 40 + matrix: + image_name: "fedora-29-libpod-0c954a67" + + timeout_in: 20m + + script: + - $SCRIPT_BASE/setup_environment.sh + - git fetch --depth $CIRRUS_CLONE_DEPTH origin $CIRRUS_BASE_BRANCH + - env GOPATH=/var/tmp/go/ make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH + # This task does the unit and integration testing for every platform testing_task: depends_on: - "gating" + - "build_each_commit" gce_instance: image_project: "libpod-218412" @@ -31,6 +31,10 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ExportImage(name: string, destination: string, compress: bool, tags: []string) string](#ExportImage) +[func GenerateKube() NotImplemented](#GenerateKube) + +[func GenerateKubeService() NotImplemented](#GenerateKubeService) + [func GetAttachSockets(name: string) Sockets](#GetAttachSockets) [func GetContainer(name: string) ListContainerData](#GetContainer) @@ -99,6 +103,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func RenameContainer() NotImplemented](#RenameContainer) +[func ReplayKube() NotImplemented](#ReplayKube) + [func ResizeContainerTty() NotImplemented](#ResizeContainerTty) [func RestartContainer(name: string, timeout: int) string](#RestartContainer) @@ -358,6 +364,18 @@ a booleon option to force compression. It also takes in a string array of tags tags of the same image to a tarball (each tag should be of the form <image>:<tag>). Upon completion, the ID of the image is returned. If the image cannot be found in local storage, an [ImageNotFound](#ImageNotFound) error will be returned. See also [ImportImage](ImportImage). +### <a name="GenerateKube"></a>func GenerateKube +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method GenerateKube() [NotImplemented](#NotImplemented)</div> +GenerateKube generates a Kubernetes v1 Pod description of a Podman container or pod +and its containers. The description is in YAML. See also [ReplayKube](ReplayKube). +### <a name="GenerateKubeService"></a>func GenerateKubeService +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method GenerateKubeService() [NotImplemented](#NotImplemented)</div> +GenerateKubeService generates a Kubernetes v1 Service description of a Podman container or pod +and its containers. The description is in YAML. See also [GenerateKube](GenerateKube). ### <a name="GetAttachSockets"></a>func GetAttachSockets <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -808,6 +826,12 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.RemovePod '{"name": "62f4 method RenameContainer() [NotImplemented](#NotImplemented)</div> This method has not be implemented yet. +### <a name="ReplayKube"></a>func ReplayKube +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ReplayKube() [NotImplemented](#NotImplemented)</div> +ReplayKube recreates a pod and its containers based on a Kubernetes v1 Pod description (in YAML) +like that created by GenerateKube. See also [GenerateKube](GenerateKube). ### <a name="ResizeContainerTty"></a>func ResizeContainerTty <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e921dcf3..32ed94ad4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -192,6 +192,8 @@ from the repository root, with the command: sudo podman build -t quay.io/libpod/gate:latest -f contrib/gate/Dockerfile . ``` +***N/B:*** **don't miss the dot (.) at the end, it's really important** + The container executes 'make' by default, on a copy of the repository. This avoids changing or leaving build artifacts in your working directory. Execution does not require any special permissions from the host. However, @@ -199,7 +201,7 @@ the repository root must be bind-mounted into the container at '/usr/src/libpod'. For example, running `make lint` is done (from the repository root) with the command: -``sudo podman run -it --rm -v $PWD:/usr/src/libpod:z quay.io/libpod/gate:latest lint`` +``sudo podman run -it --rm -v $PWD:/usr/src/libpod:ro --security-opt label=disable quay.io/libpod/gate:latest lint`` ### Integration Tests @@ -5,6 +5,7 @@ HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD PROJECT := github.com/containers/libpod +GIT_BASE_BRANCH ?= origin/master GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) GIT_BRANCH_CLEAN ?= $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g") LIBPOD_IMAGE ?= libpod_dev$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN)) @@ -351,6 +352,10 @@ API.md: cmd/podman/varlink/io.podman.varlink validate: gofmt .gitvalidation +build-all-new-commits: + # Validate that all the commits build on top of $(GIT_BASE_BRANCH) + git rebase $(GIT_BASE_BRANCH) -x make + .PHONY: \ .gopathok \ binaries \ diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 870eb28d6..2b31a6423 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -146,37 +146,10 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container return nil, nil, err } - runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) - if err != nil { - return nil, nil, err - } - - options, err := createConfig.GetContainerCreateOptions(runtime) - if err != nil { - return nil, nil, err - } - - became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, runtime) + ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx) if err != nil { return nil, nil, err } - if became { - os.Exit(ret) - } - - ctr, err := runtime.NewContainer(ctx, runtimeSpec, options...) - if err != nil { - return nil, nil, err - } - - createConfigJSON, err := json.Marshal(createConfig) - if err != nil { - return nil, nil, err - } - if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { - return nil, nil, err - } - if cidFile != nil { _, err = cidFile.WriteString(ctr.ID()) if err != nil { @@ -913,3 +886,37 @@ func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *l } return rootless.BecomeRootInUserNS() } + +func createContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context) (*libpod.Container, error) { + runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) + if err != nil { + return nil, err + } + + options, err := createConfig.GetContainerCreateOptions(r) + if err != nil { + return nil, err + } + + became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, r) + if err != nil { + return nil, err + } + if became { + os.Exit(ret) + } + + ctr, err := r.NewContainer(ctx, runtimeSpec, options...) + if err != nil { + return nil, err + } + + createConfigJSON, err := json.Marshal(createConfig) + if err != nil { + return nil, err + } + if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { + return nil, err + } + return ctr, nil +} diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go index 8f2f0de32..f63bd431b 100644 --- a/cmd/podman/generate_kube.go +++ b/cmd/podman/generate_kube.go @@ -33,7 +33,6 @@ var ( } ) -// generateKubeYAMLCmdgenerates or replays kube func generateKubeYAMLCmd(c *cli.Context) error { var ( podYAML *v1.Pod diff --git a/cmd/podman/image.go b/cmd/podman/image.go index 95af36df5..e978b9cf5 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -19,6 +19,7 @@ var ( rmImageCommand, saveCommand, tagCommand, + trustCommand, } imageDescription = "Manage images" diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 2db6c5dec..f47a75761 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -90,6 +90,7 @@ func main() { portCommand, pullCommand, pushCommand, + playCommand, restartCommand, rmCommand, rmiCommand, diff --git a/cmd/podman/play.go b/cmd/podman/play.go new file mode 100644 index 000000000..adbab3480 --- /dev/null +++ b/cmd/podman/play.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + playSubCommands = []cli.Command{ + playKubeCommand, + } + + playDescription = "Play a pod and its containers from a structured file." + playCommand = cli.Command{ + Name: "play", + Usage: "play a container or pod", + Description: playDescription, + ArgsUsage: "", + Subcommands: playSubCommands, + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + Hidden: true, + } +) diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go new file mode 100644 index 000000000..f165c5f0f --- /dev/null +++ b/cmd/podman/play_kube.go @@ -0,0 +1,245 @@ +package main + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + image2 "github.com/containers/libpod/libpod/image" + ns "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/spec" + "github.com/containers/storage" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" + "k8s.io/api/core/v1" +) + +var ( + playKubeFlags = []cli.Flag{ + cli.StringFlag{ + Name: "authfile", + Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", + }, + cli.StringFlag{ + Name: "cert-dir", + Usage: "`pathname` of a directory containing TLS certificates and keys", + }, + cli.StringFlag{ + Name: "creds", + Usage: "`credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", + }, + cli.BoolFlag{ + Name: "quiet, q", + Usage: "Suppress output information when pulling images", + }, + cli.StringFlag{ + Name: "signature-policy", + Usage: "`pathname` of signature policy file (not usually used)", + }, + cli.BoolTFlag{ + Name: "tls-verify", + Usage: "require HTTPS and verify certificates when contacting registries (default: true)", + }, + } + playKubeDescription = "Play a Pod and its containers based on a Kubrernetes YAML" + playKubeCommand = cli.Command{ + Name: "kube", + Usage: "Play a pod based on Kubernetes YAML", + Description: playKubeDescription, + Action: playKubeYAMLCmd, + Flags: sortFlags(playKubeFlags), + ArgsUsage: "kubernetes YAML file", + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } +) + +func playKubeYAMLCmd(c *cli.Context) error { + var ( + podOptions []libpod.PodCreateOption + podYAML v1.Pod + registryCreds *types.DockerAuthConfig + containers []*libpod.Container + writer io.Writer + ) + + ctx := getContext() + if rootless.IsRootless() { + return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") + } + args := c.Args() + if len(args) > 1 { + return errors.New("you can only play one kubernetes file at a time") + } + if len(args) < 1 { + return errors.New("you must supply at least one file") + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + content, err := ioutil.ReadFile(args[0]) + if err != nil { + return err + } + + if err := yaml.Unmarshal(content, &podYAML); err != nil { + return errors.Wrapf(err, "unable to read %s as YAML", args[0]) + } + + podOptions = append(podOptions, libpod.WithInfraContainer()) + podOptions = append(podOptions, libpod.WithPodName(podYAML.ObjectMeta.Name)) + // TODO for now we just used the default kernel namespaces; we need to add/subtract this from yaml + + nsOptions, err := shared.GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) + if err != nil { + return err + } + podOptions = append(podOptions, nsOptions...) + podPorts := getPodPorts(podYAML.Spec.Containers) + podOptions = append(podOptions, libpod.WithInfraContainerPorts(podPorts)) + + // Create the Pod + pod, err := runtime.NewPod(ctx, podOptions...) + if err != nil { + return err + } + // Print the Pod's ID + fmt.Println(pod.ID()) + + podInfraID, err := pod.InfraContainerID() + if err != nil { + return err + } + + namespaces := map[string]string{ + // Disabled during code review per mheon + //"pid": fmt.Sprintf("container:%s", podInfraID), + "net": fmt.Sprintf("container:%s", podInfraID), + "user": fmt.Sprintf("container:%s", podInfraID), + "ipc": fmt.Sprintf("container:%s", podInfraID), + "uts": fmt.Sprintf("container:%s", podInfraID), + } + if !c.Bool("quiet") { + writer = os.Stderr + } + + dockerRegistryOptions := image2.DockerRegistryOptions{ + DockerRegistryCreds: registryCreds, + DockerCertPath: c.String("cert-dir"), + } + if c.IsSet("tls-verify") { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + } + + for _, container := range podYAML.Spec.Containers { + newImage, err := runtime.ImageRuntime().New(ctx, container.Image, c.String("signature-policy"), c.String("authfile"), writer, &dockerRegistryOptions, image2.SigningOptions{}, false) + if err != nil { + return err + } + createConfig := kubeContainerToCreateConfig(container, runtime, newImage, namespaces) + if err != nil { + return err + } + ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx) + if err != nil { + return err + } + containers = append(containers, ctr) + } + + // start the containers + for _, ctr := range containers { + if err := ctr.Start(ctx); err != nil { + // Making this a hard failure here to avoid a mess + // the other containers are in created status + return err + } + fmt.Println(ctr.ID()) + } + + return nil +} + +// getPodPorts converts a slice of kube container descriptions to an +// array of ocicni portmapping descriptions usable in libpod +func getPodPorts(containers []v1.Container) []ocicni.PortMapping { + var infraPorts []ocicni.PortMapping + for _, container := range containers { + for _, p := range container.Ports { + portBinding := ocicni.PortMapping{ + HostPort: p.HostPort, + ContainerPort: p.ContainerPort, + Protocol: strings.ToLower(string(p.Protocol)), + } + if p.HostIP != "" { + logrus.Debug("HostIP on port bindings is not supported") + } + infraPorts = append(infraPorts, portBinding) + } + } + return infraPorts +} + +// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container +func kubeContainerToCreateConfig(containerYAML v1.Container, runtime *libpod.Runtime, newImage *image2.Image, namespaces map[string]string) *createconfig.CreateConfig { + var ( + containerConfig createconfig.CreateConfig + envs map[string]string + ) + + containerConfig.Runtime = runtime + containerConfig.Image = containerYAML.Image + containerConfig.ImageID = newImage.ID() + containerConfig.Name = containerYAML.Name + containerConfig.Tty = containerYAML.TTY + containerConfig.WorkDir = containerYAML.WorkingDir + if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil { + containerConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem + } + if containerYAML.SecurityContext.Privileged != nil { + containerConfig.Privileged = *containerYAML.SecurityContext.Privileged + } + + if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil { + containerConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation + } + + containerConfig.Command = containerYAML.Command + containerConfig.StopSignal = 15 + + // If the user does not pass in ID mappings, just set to basics + if containerConfig.IDMappings == nil { + containerConfig.IDMappings = &storage.IDMappingOptions{} + } + + containerConfig.NetMode = ns.NetworkMode(namespaces["net"]) + containerConfig.IpcMode = ns.IpcMode(namespaces["ipc"]) + containerConfig.UtsMode = ns.UTSMode(namespaces["uts"]) + // disabled in code review per mheon + //containerConfig.PidMode = ns.PidMode(namespaces["pid"]) + containerConfig.UsernsMode = ns.UsernsMode(namespaces["user"]) + + if len(containerYAML.Env) > 0 { + envs = make(map[string]string) + } + // Environment Variables + for _, e := range containerYAML.Env { + envs[e.Name] = e.Value + } + containerConfig.Env = envs + return &containerConfig +} diff --git a/cmd/podman/trust.go b/cmd/podman/trust.go new file mode 100644 index 000000000..7c404cd3f --- /dev/null +++ b/cmd/podman/trust.go @@ -0,0 +1,293 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "sort" + + "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/formats" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/trust" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + setTrustFlags = []cli.Flag{ + cli.StringFlag{ + Name: "type, t", + Usage: "Trust type, accept values: signedBy(default), accept, reject.", + Value: "signedBy", + }, + cli.StringSliceFlag{ + Name: "pubkeysfile, f", + Usage: `Path of installed public key(s) to trust for TARGET. + Absolute path to keys is added to policy.json. May + used multiple times to define multiple public keys. + File(s) must exist before using this command.`, + }, + cli.StringFlag{ + Name: "policypath", + Hidden: true, + }, + } + showTrustFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "raw", + Usage: "Output raw policy file", + }, + cli.BoolFlag{ + Name: "json, j", + Usage: "Output as json", + }, + cli.StringFlag{ + Name: "policypath", + Hidden: true, + }, + cli.StringFlag{ + Name: "registrypath", + Hidden: true, + }, + } + + setTrustDescription = "Set default trust policy or add a new trust policy for a registry" + setTrustCommand = cli.Command{ + Name: "set", + Usage: "Set default trust policy or a new trust policy for a registry", + Description: setTrustDescription, + Flags: sortFlags(setTrustFlags), + ArgsUsage: "default | REGISTRY[/REPOSITORY]", + Action: setTrustCmd, + OnUsageError: usageErrorHandler, + } + + showTrustDescription = "Display trust policy for the system" + showTrustCommand = cli.Command{ + Name: "show", + Usage: "Display trust policy for the system", + Description: showTrustDescription, + Flags: sortFlags(showTrustFlags), + Action: showTrustCmd, + ArgsUsage: "", + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } + + trustSubCommands = []cli.Command{ + setTrustCommand, + showTrustCommand, + } + + trustDescription = fmt.Sprintf(`Manages the trust policy of the host system. (%s) + Trust policy describes a registry scope that must be signed by public keys.`, getDefaultPolicyPath()) + trustCommand = cli.Command{ + Name: "trust", + Usage: "Manage container image trust policy", + Description: trustDescription, + ArgsUsage: "{set,show} ...", + Subcommands: trustSubCommands, + OnUsageError: usageErrorHandler, + } +) + +func showTrustCmd(c *cli.Context) error { + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not create runtime") + } + + var ( + policyPath string + systemRegistriesDirPath string + ) + if c.IsSet("policypath") { + policyPath = c.String("policypath") + } else { + policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) + } + policyContent, err := ioutil.ReadFile(policyPath) + if err != nil { + return errors.Wrapf(err, "unable to read %s", policyPath) + } + if c.IsSet("registrypath") { + systemRegistriesDirPath = c.String("registrypath") + } else { + systemRegistriesDirPath = trust.RegistriesDirPath(runtime.SystemContext()) + } + + if c.Bool("raw") { + _, err := os.Stdout.Write(policyContent) + if err != nil { + return errors.Wrap(err, "could not read trust policies") + } + return nil + } + + var policyContentStruct trust.PolicyContent + if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { + return errors.Errorf("could not read trust policies") + } + policyJSON, err := trust.GetPolicyJSON(policyContentStruct, systemRegistriesDirPath) + if err != nil { + return errors.Wrapf(err, "error reading registry config file") + } + if c.Bool("json") { + var outjson interface{} + outjson = policyJSON + out := formats.JSONStruct{Output: outjson} + return formats.Writer(out).Out() + } + + sortedRepos := sortPolicyJSONKey(policyJSON) + type policydefault struct { + Repo string + Trusttype string + GPGid string + Sigstore string + } + var policyoutput []policydefault + for _, repo := range sortedRepos { + repoval := policyJSON[repo] + var defaultstruct policydefault + defaultstruct.Repo = repo + if repoval["type"] != nil { + defaultstruct.Trusttype = trustTypeDescription(repoval["type"].(string)) + } + if repoval["keys"] != nil && len(repoval["keys"].([]string)) > 0 { + defaultstruct.GPGid = trust.GetGPGId(repoval["keys"].([]string)) + } + if repoval["sigstore"] != nil { + defaultstruct.Sigstore = repoval["sigstore"].(string) + } + policyoutput = append(policyoutput, defaultstruct) + } + var output []interface{} + for _, ele := range policyoutput { + output = append(output, interface{}(ele)) + } + out := formats.StdoutTemplateArray{Output: output, Template: "{{.Repo}}\t{{.Trusttype}}\t{{.GPGid}}\t{{.Sigstore}}"} + return formats.Writer(out).Out() +} + +func setTrustCmd(c *cli.Context) error { + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not create runtime") + } + + args := c.Args() + if len(args) != 1 { + return errors.Errorf("default or a registry name must be specified") + } + valid, err := image.IsValidImageURI(args[0]) + if err != nil || !valid { + return errors.Wrapf(err, "invalid image uri %s", args[0]) + } + + trusttype := c.String("type") + if !isValidTrustType(trusttype) { + return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", trusttype) + } + if trusttype == "accept" { + trusttype = "insecureAcceptAnything" + } + + pubkeysfile := c.StringSlice("pubkeysfile") + if len(pubkeysfile) == 0 && trusttype == "signedBy" { + return errors.Errorf("At least one public key must be defined for type 'signedBy'") + } + + var policyPath string + if c.IsSet("policypath") { + policyPath = c.String("policypath") + } else { + policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) + } + var policyContentStruct trust.PolicyContent + _, err = os.Stat(policyPath) + if !os.IsNotExist(err) { + policyContent, err := ioutil.ReadFile(policyPath) + if err != nil { + return errors.Wrapf(err, "unable to read %s", policyPath) + } + if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { + return errors.Errorf("could not read trust policies") + } + } + var newReposContent []trust.RepoContent + if len(pubkeysfile) != 0 { + for _, filepath := range pubkeysfile { + newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype, KeyType: "GPGKeys", KeyPath: filepath}) + } + } else { + newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype}) + } + if args[0] == "default" { + policyContentStruct.Default = newReposContent + } else { + exists := false + for transport, transportval := range policyContentStruct.Transports { + _, exists = transportval[args[0]] + if exists { + policyContentStruct.Transports[transport][args[0]] = newReposContent + break + } + } + if !exists { + if policyContentStruct.Transports == nil { + policyContentStruct.Transports = make(map[string]trust.RepoMap) + } + if policyContentStruct.Transports["docker"] == nil { + policyContentStruct.Transports["docker"] = make(map[string][]trust.RepoContent) + } + policyContentStruct.Transports["docker"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...) + } + } + + data, err := json.MarshalIndent(policyContentStruct, "", " ") + if err != nil { + return errors.Wrapf(err, "error setting trust policy") + } + err = ioutil.WriteFile(policyPath, data, 0644) + if err != nil { + return errors.Wrapf(err, "error setting trust policy") + } + return nil +} + +var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} + +func trustTypeDescription(trustType string) string { + trustDescription, exist := typeDescription[trustType] + if !exist { + logrus.Warnf("invalid trust type %s", trustType) + } + return trustDescription +} + +func sortPolicyJSONKey(m map[string]map[string]interface{}) []string { + keys := make([]string, len(m)) + i := 0 + for k := range m { + keys[i] = k + i++ + } + sort.Strings(keys) + return keys +} + +func isValidTrustType(t string) bool { + if t == "accept" || t == "insecureAcceptAnything" || t == "reject" || t == "signedBy" { + return true + } + return false +} + +func getDefaultPolicyPath() string { + return trust.DefaultPolicyPath(&types.SystemContext{}) +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 376bbc950..c1b7c703a 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -1016,6 +1016,18 @@ method UnmountContainer(name: string, force: bool) -> () # This function is not implemented yet. method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) +# GenerateKube generates a Kubernetes v1 Pod description of a Podman container or pod +# and its containers. The description is in YAML. See also [ReplayKube](ReplayKube). +method GenerateKube() -> (notimplemented: NotImplemented) + +# GenerateKubeService generates a Kubernetes v1 Service description of a Podman container or pod +# and its containers. The description is in YAML. See also [GenerateKube](GenerateKube). +method GenerateKubeService() -> (notimplemented: NotImplemented) + +# ReplayKube recreates a pod and its containers based on a Kubernetes v1 Pod description (in YAML) +# like that created by GenerateKube. See also [GenerateKube](GenerateKube). +method ReplayKube() -> (notimplemented: NotImplemented) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/completions/bash/podman b/completions/bash/podman index 4702ae0e0..2ce70a1f5 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -895,6 +895,26 @@ _podman_generate() { ;; esac } + +_podman_play() { + local boolean_options=" + --help + -h + " + subcommands=" + kube + " + __podman_subcommands "$subcommands $aliases" && return + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + ;; + *) + COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) + ;; + esac +} _podman_container() { local boolean_options=" --help @@ -2247,6 +2267,22 @@ _podman_generate_kube() { --service " +_podman_play_kube() { + local options_with_args=" + --authfile + --cert-dir + --creds + --signature-policy + " + + local boolean_options=" + -h + --help + --quiet + -q + --tls-verify + " + _podman_container_runlabel() { local options_with_args=" --authfile @@ -2750,6 +2786,7 @@ _podman_podman() { ps pull push + play restart rm rmi diff --git a/contrib/gate/Dockerfile b/contrib/gate/Dockerfile index 0c0e4aaf9..f9b57a6da 100644 --- a/contrib/gate/Dockerfile +++ b/contrib/gate/Dockerfile @@ -1,4 +1,4 @@ -FROM fedora:28 +FROM fedora:29 RUN dnf -y install \ atomic-registries \ btrfs-progs-devel \ diff --git a/docs/podman-generate-kube.1.md b/docs/podman-generate-kube.1.md index 396f69615..5236f23fe 100644 --- a/docs/podman-generate-kube.1.md +++ b/docs/podman-generate-kube.1.md @@ -145,7 +145,7 @@ status: ``` ## SEE ALSO -podman(1), podman-container, podman-pod +podman(1), podman-container, podman-pod, podman-play # HISTORY Decemeber 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-image-trust.1.md b/docs/podman-image-trust.1.md new file mode 100644 index 000000000..24209698c --- /dev/null +++ b/docs/podman-image-trust.1.md @@ -0,0 +1,81 @@ +% podman-image-trust "1" + +# NAME +podman\-trust - Manage container image trust policy + + +# SYNOPSIS +**podman image trust set|show** +[**-h**|**--help**] +[**-j**|**--json**] +[**--raw**] +[**-f**|**--pubkeysfile** KEY1 [**f**|**--pubkeysfile** KEY2,...]] +[**-t**|**--type** signedBy|accept|reject] +REGISTRY[/REPOSITORY] + +# DESCRIPTION +Manages the trust policy of the host system. Trust policy describes +a registry scope (registry and/or repository) that must be signed by public keys. Trust +is defined in **/etc/containers/policy.json**. Trust is enforced when a user attempts to pull +an image from a registry. + +Trust scope is evaluated by most specific to least specific. In other words, policy may +be defined for an entire registry, but refined for a particular repository in that +registry. See below for examples. + +Trust **type** provides a way to whitelist ("accept") or blacklist +("reject") registries. + +Trust may be updated using the command **podman image trust set** for an existing trust scope. + +# OPTIONS +**-h** **--help** + Print usage statement. + +**-f** **--pubkeysfile** + A path to an exported public key on the local system. Key paths + will be referenced in policy.json. Any path may be used but path + **/etc/pki/containers** is recommended. Option may be used multiple times to + require an image be sigend by multiple keys. One of **--pubkeys** or + **--pubkeysfile** is required for **signedBy** type. + +**-t** **--type** + The trust type for this policy entry. Accepted values: + **signedBy** (default): Require signatures with corresponding list of + public keys + **accept**: do not require any signatures for this + registry scope + **reject**: do not accept images for this registry scope + +# show OPTIONS + +**--raw** + Output trust policy file as raw JSON + +**-j** **--json** + Output trust as JSON for machine parsing + +# EXAMPLES + +Accept all unsigned images from a registry + + podman image trust set --type accept docker.io + +Modify default trust policy + + podman image trust set -t reject default + +Display system trust policy + + podman image trust show + +Display trust policy file + + podman image trust show --raw + +Display trust as JSON + + podman image trust show --json + +# HISTORY +December 2018, originally compiled by Qi Wang (qiwan at redhat dot com) diff --git a/docs/podman-image.1.md b/docs/podman-image.1.md index 8b812af11..8aa7cee64 100644 --- a/docs/podman-image.1.md +++ b/docs/podman-image.1.md @@ -26,6 +26,7 @@ The image command allows you to manage images | rm | [podman-rm(1)](podman-rmi.1.md) | Removes one or more locally stored images. | | save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. | | tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. | +| trust | [podman-image-trust(1)](podman-image-trust.1.md) | Manage container image trust policy. ## SEE ALSO podman diff --git a/docs/podman-play-kube.1.md b/docs/podman-play-kube.1.md new file mode 100644 index 000000000..3fd9746a5 --- /dev/null +++ b/docs/podman-play-kube.1.md @@ -0,0 +1,78 @@ +% podman-play-kube Podman Man Pages +% Brent Baude +% December 2018 +# NAME +podman-play-kube - Create pods and containers based on Kubernetes YAML + +# SYNOPSIS +**podman play kube ** +[**-h**|**--help**] +[**--authfile**] +[**--cert-dir**] +[**--creds**] +[***-q** | **--quiet**] +[**--signature-policy**] +[**--tls-verify**] +kubernetes_input.yml + +# DESCRIPTION +**podman play kube** will read in a structured file of Kubernetes YAML. It will then recreate +the pod and containers described in the YAML. The containers within the pod are then started and +the ID of the new Pod is output. + +Ideally the input file would be one created by Podman. This would guarantee a smooth import and expected results. + +# OPTIONS: + +**--authfile** + +Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. + +Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE +environment variable. `export REGISTRY_AUTH_FILE=path` + +**--cert-dir** *path* + +Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry. +Default certificates directory is _/etc/containers/certs.d_. + +**--creds** + +The [username[:password]] to use to authenticate with the registry if required. +If one or both values are not supplied, a command line prompt will appear and the +value can be entered. The password is entered without echo. + +**--quiet, -q** + +Suppress output information when pulling images + +**--signature-policy="PATHNAME"** + +Pathname of a signature policy file to use. It is not recommended that this +option be used, as the default behavior of using the system-wide default policy +(frequently */etc/containers/policy.json*) is most often preferred. + +**--tls-verify** + +Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true, +then TLS verification will be used. If set to false, then TLS verification will not be used. If not specified, +TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. + +**--help**, **-h** + +Print usage statement + +## Examples ## + +Recreate the pod and containers as described in a file called `demo.yml` +``` +$ podman play kube demo.yml +52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 +``` + +## SEE ALSO +podman(1), podman-container(1), podman-pod(1), podman-generate(1), podman-play(1) + +# HISTORY +Decemeber 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-play.1.md b/docs/podman-play.1.md new file mode 100644 index 000000000..c703c1455 --- /dev/null +++ b/docs/podman-play.1.md @@ -0,0 +1,20 @@ +% podman-play(1) + +## NAME +podman\-container - play pods and containers based on a structured input file + +## SYNOPSIS +**podman play** *subcommand* + +## DESCRIPTION +The play command will recreate pods and containers based on the input from a structured (like YAML) +file input. Containers will be automatically started. + +## COMMANDS + +| Command | Man Page | Description | +| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- | +| kube | [podman-play-kube(1)](podman-play-kube.1.md) | Recreate pods and containers based on Kubernetes YAML. + +## SEE ALSO +podman, podman-pod(1), podman-container(1), podman-generate(1), podman-play(1), podman-play-kube(1) diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md index 659973b28..f8332c820 100644 --- a/docs/tutorials/podman_tutorial.md +++ b/docs/tutorials/podman_tutorial.md @@ -84,6 +84,12 @@ cd $GOPATH/src/github.com/containernetworking/plugins sudo mkdir -p /usr/libexec/cni sudo cp bin/* /usr/libexec/cni ``` +#### Installing CNI config +Add a most basic network config +```console +mkdir -p /etc/cni/net.d +curl -qsSL https://raw.githubusercontent.com/containers/libpod/master/cni/87-podman-bridge.conflist | tee /etc/cni/net.d/99-loopback.conf +``` #### Installing runc ```console git clone https://github.com/opencontainers/runc.git $GOPATH/src/github.com/opencontainers/runc diff --git a/libpod/image/utils.go b/libpod/image/utils.go index 9a75ca6dc..b944de1bb 100644 --- a/libpod/image/utils.go +++ b/libpod/image/utils.go @@ -2,6 +2,8 @@ package image import ( "io" + "net/url" + "regexp" "strings" cp "github.com/containers/image/copy" @@ -117,3 +119,23 @@ func GetAdditionalTags(images []string) ([]reference.NamedTagged, error) { } return allTags, nil } + +// IsValidImageURI checks if image name has valid format +func IsValidImageURI(imguri string) (bool, error) { + uri := "http://" + imguri + u, err := url.Parse(uri) + if err != nil { + return false, errors.Wrapf(err, "invalid image uri: %s", imguri) + } + reg := regexp.MustCompile(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`) + ret := reg.FindAllString(u.Host, -1) + if len(ret) == 0 { + return false, errors.Wrapf(err, "invalid image uri: %s", imguri) + } + reg = regexp.MustCompile(`^[a-z0-9-:\./]*$`) + ret = reg.FindAllString(u.Fragment, -1) + if len(ret) == 0 { + return false, errors.Wrapf(err, "invalid image uri: %s", imguri) + } + return true, nil +} diff --git a/libpod/kube.go b/libpod/kube.go index c164ca0c5..f34805e39 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -30,7 +30,10 @@ func (c *Container) GenerateForKube() (*v1.Pod, error) { // one v1.Pod description func (p *Pod) GenerateForKube() (*v1.Pod, []v1.ServicePort, error) { // Generate the v1.Pod yaml description - var servicePorts []v1.ServicePort + var ( + servicePorts []v1.ServicePort + ports []v1.ContainerPort + ) allContainers, err := p.allContainers() if err != nil { @@ -51,13 +54,13 @@ func (p *Pod) GenerateForKube() (*v1.Pod, []v1.ServicePort, error) { return nil, servicePorts, err } - ports, err := ocicniPortMappingToContainerPort(infraContainer.config.PortMappings) + ports, err = ocicniPortMappingToContainerPort(infraContainer.config.PortMappings) if err != nil { return nil, servicePorts, err } servicePorts = containerPortsToServicePorts(ports) } - pod, err := p.podWithContainers(allContainers) + pod, err := p.podWithContainers(allContainers, ports) return pod, servicePorts, err } @@ -124,18 +127,27 @@ func containersToServicePorts(containers []v1.Container) []v1.ServicePort { return sps } -func (p *Pod) podWithContainers(containers []*Container) (*v1.Pod, error) { - var podContainers []v1.Container +func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPort) (*v1.Pod, error) { + var ( + podContainers []v1.Container + ) + first := true for _, ctr := range containers { - result, err := containerToV1Container(ctr) - if err != nil { - return nil, err - } if !ctr.IsInfra() { + result, err := containerToV1Container(ctr) + if err != nil { + return nil, err + } + // We add the original port declarations from the libpod infra container + // to the first kubernetes container description because otherwise we loose + // the original container/port bindings. + if first && len(ports) > 0 { + result.Ports = ports + first = false + } podContainers = append(podContainers, result) } } - return addContainersToPodObject(podContainers, p.Name()), nil } @@ -150,7 +162,7 @@ func addContainersToPodObject(containers []v1.Container, podName string) *v1.Pod labels["app"] = removeUnderscores(podName) om := v12.ObjectMeta{ // The name of the pod is container_name-libpod - Name: fmt.Sprintf("%s-libpod", removeUnderscores(podName)), + Name: fmt.Sprintf("%s", removeUnderscores(podName)), Labels: labels, // CreationTimestamp seems to be required, so adding it; in doing so, the timestamp // will reflect time this is run (not container create time) because the conversion diff --git a/libpod/runtime.go b/libpod/runtime.go index 82473aae9..2dfebf565 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -877,3 +877,8 @@ func (r *Runtime) generateName() (string, error) { func (r *Runtime) ImageRuntime() *image.Runtime { return r.imageRuntime } + +// SystemContext returns the imagecontext +func (r *Runtime) SystemContext() *types.SystemContext { + return r.imageContext +} diff --git a/pkg/trust/trust.go b/pkg/trust/trust.go new file mode 100644 index 000000000..efc760364 --- /dev/null +++ b/pkg/trust/trust.go @@ -0,0 +1,250 @@ +package trust + +import ( + "bufio" + "encoding/base64" + "encoding/json" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "unsafe" + + "github.com/containers/image/types" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + yaml "gopkg.in/yaml.v2" +) + +// PolicyContent struct for policy.json file +type PolicyContent struct { + Default []RepoContent `json:"default"` + Transports TransportsContent `json:"transports"` +} + +// RepoContent struct used under each repo +type RepoContent struct { + Type string `json:"type"` + KeyType string `json:"keyType,omitempty"` + KeyPath string `json:"keyPath,omitempty"` + KeyData string `json:"keyData,omitempty"` + SignedIdentity json.RawMessage `json:"signedIdentity,omitempty"` +} + +// RepoMap map repo name to policycontent for each repo +type RepoMap map[string][]RepoContent + +// TransportsContent struct for content under "transports" +type TransportsContent map[string]RepoMap + +// RegistryConfiguration is one of the files in registriesDirPath configuring lookaside locations, or the result of merging them all. +// NOTE: Keep this in sync with docs/registries.d.md! +type RegistryConfiguration struct { + DefaultDocker *RegistryNamespace `json:"default-docker"` + // The key is a namespace, using fully-expanded Docker reference format or parent namespaces (per dockerReference.PolicyConfiguration*), + Docker map[string]RegistryNamespace `json:"docker"` +} + +// RegistryNamespace defines lookaside locations for a single namespace. +type RegistryNamespace struct { + SigStore string `json:"sigstore"` // For reading, and if SigStoreStaging is not present, for writing. + SigStoreStaging string `json:"sigstore-staging"` // For writing only. +} + +// DefaultPolicyPath returns a path to the default policy of the system. +func DefaultPolicyPath(sys *types.SystemContext) string { + systemDefaultPolicyPath := "/etc/containers/policy.json" + if sys != nil { + if sys.SignaturePolicyPath != "" { + return sys.SignaturePolicyPath + } + if sys.RootForImplicitAbsolutePaths != "" { + return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath) + } + } + return systemDefaultPolicyPath +} + +// RegistriesDirPath returns a path to registries.d +func RegistriesDirPath(sys *types.SystemContext) string { + systemRegistriesDirPath := "/etc/containers/registries.d" + if sys != nil { + if sys.RegistriesDirPath != "" { + return sys.RegistriesDirPath + } + if sys.RootForImplicitAbsolutePaths != "" { + return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath) + } + } + return systemRegistriesDirPath +} + +// LoadAndMergeConfig loads configuration files in dirPath +func LoadAndMergeConfig(dirPath string) (*RegistryConfiguration, error) { + mergedConfig := RegistryConfiguration{Docker: map[string]RegistryNamespace{}} + dockerDefaultMergedFrom := "" + nsMergedFrom := map[string]string{} + + dir, err := os.Open(dirPath) + if err != nil { + if os.IsNotExist(err) { + return &mergedConfig, nil + } + return nil, err + } + configNames, err := dir.Readdirnames(0) + if err != nil { + return nil, err + } + for _, configName := range configNames { + if !strings.HasSuffix(configName, ".yaml") { + continue + } + configPath := filepath.Join(dirPath, configName) + configBytes, err := ioutil.ReadFile(configPath) + if err != nil { + return nil, err + } + var config RegistryConfiguration + err = yaml.Unmarshal(configBytes, &config) + if err != nil { + return nil, errors.Wrapf(err, "Error parsing %s", configPath) + } + if config.DefaultDocker != nil { + if mergedConfig.DefaultDocker != nil { + return nil, errors.Errorf(`Error parsing signature storage configuration: "default-docker" defined both in "%s" and "%s"`, + dockerDefaultMergedFrom, configPath) + } + mergedConfig.DefaultDocker = config.DefaultDocker + dockerDefaultMergedFrom = configPath + } + for nsName, nsConfig := range config.Docker { // includes config.Docker == nil + if _, ok := mergedConfig.Docker[nsName]; ok { + return nil, errors.Errorf(`Error parsing signature storage configuration: "docker" namespace "%s" defined both in "%s" and "%s"`, + nsName, nsMergedFrom[nsName], configPath) + } + mergedConfig.Docker[nsName] = nsConfig + nsMergedFrom[nsName] = configPath + } + } + return &mergedConfig, nil +} + +// HaveMatchRegistry checks if trust settings for the registry have been configed in yaml file +func HaveMatchRegistry(key string, registryConfigs *RegistryConfiguration) *RegistryNamespace { + searchKey := key + if !strings.Contains(searchKey, "/") { + val, exists := registryConfigs.Docker[searchKey] + if exists { + return &val + } + } + for range strings.Split(key, "/") { + val, exists := registryConfigs.Docker[searchKey] + if exists { + return &val + } + if strings.Contains(searchKey, "/") { + searchKey = searchKey[:strings.LastIndex(searchKey, "/")] + } + } + return nil +} + +// CreateTmpFile creates a temp file under dir and writes the content into it +func CreateTmpFile(dir, pattern string, content []byte) (string, error) { + tmpfile, err := ioutil.TempFile(dir, pattern) + if err != nil { + return "", err + } + defer tmpfile.Close() + + if _, err := tmpfile.Write(content); err != nil { + return "", err + + } + return tmpfile.Name(), nil +} + +// GetGPGId return GPG identity, either bracketed <email> or ID string +// comma separated if more than one key +func GetGPGId(keys []string) string { + for _, k := range keys { + if _, err := os.Stat(k); err != nil { + decodeKey, err := base64.StdEncoding.DecodeString(k) + if err != nil { + logrus.Warnf("error decoding key data") + continue + } + tmpfileName, err := CreateTmpFile("/run/", "", decodeKey) + if err != nil { + logrus.Warnf("error creating key date temp file %s", err) + } + defer os.Remove(tmpfileName) + k = tmpfileName + } + cmd := exec.Command("gpg2", "--with-colons", k) + results, err := cmd.Output() + if err != nil { + logrus.Warnf("error get key identity: %s", err) + continue + } + resultsStr := *(*string)(unsafe.Pointer(&results)) + scanner := bufio.NewScanner(strings.NewReader(resultsStr)) + var parseduids []string + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") { + uid := strings.Split(line, ":")[9] + if uid == "" { + continue + } + parseduid := uid + if strings.Contains(uid, "<") && strings.Contains(uid, ">") { + parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0] + } + parseduids = append(parseduids, parseduid) + } + } + return strings.Join(parseduids, ",") + } + return "" +} + +// GetPolicyJSON return the struct to show policy.json in json format +func GetPolicyJSON(policyContentStruct PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) { + registryConfigs, err := LoadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return nil, err + } + + policyJSON := make(map[string]map[string]interface{}) + if len(policyContentStruct.Default) > 0 { + policyJSON["* (default)"] = make(map[string]interface{}) + policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type + } + for transname, transval := range policyContentStruct.Transports { + for repo, repoval := range transval { + policyJSON[repo] = make(map[string]interface{}) + policyJSON[repo]["type"] = repoval[0].Type + policyJSON[repo]["transport"] = transname + for _, repoele := range repoval { + keyarr := []string{} + if len(repoele.KeyPath) > 0 { + keyarr = append(keyarr, repoele.KeyPath) + } + if len(repoele.KeyData) > 0 { + keyarr = append(keyarr, string(repoele.KeyData)) + } + policyJSON[repo]["keys"] = keyarr + } + policyJSON[repo]["sigstore"] = "" + registryNamespace := HaveMatchRegistry(repo, registryConfigs) + if registryNamespace != nil { + policyJSON[repo]["sigstore"] = registryNamespace.SigStore + } + } + } + return policyJSON, nil +} diff --git a/test/e2e/trust_test.go b/test/e2e/trust_test.go new file mode 100644 index 000000000..bbf09eca4 --- /dev/null +++ b/test/e2e/trust_test.go @@ -0,0 +1,72 @@ +package integration + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman trust", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("podman image trust show", func() { + path, err := os.Getwd() + if err != nil { + os.Exit(1) + } + session := podmanTest.Podman([]string{"image", "trust", "show", "--registrypath", filepath.Dir(path), "--policypath", filepath.Join(filepath.Dir(path), "policy.json")}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + outArray := session.OutputToStringArray() + Expect(len(outArray)).To(Equal(3)) + Expect(outArray[0]).Should(ContainSubstring("accept")) + Expect(outArray[1]).Should(ContainSubstring("reject")) + Expect(outArray[2]).Should(ContainSubstring("signed")) + }) + + It("podman image trust set", func() { + path, err := os.Getwd() + if err != nil { + os.Exit(1) + } + session := podmanTest.Podman([]string{"image", "trust", "set", "--policypath", filepath.Join(filepath.Dir(path), "trust_set_test.json"), "-t", "accept", "default"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + var teststruct map[string][]map[string]string + policyContent, err := ioutil.ReadFile(filepath.Join(filepath.Dir(path), "trust_set_test.json")) + if err != nil { + os.Exit(1) + } + err = json.Unmarshal(policyContent, &teststruct) + if err != nil { + os.Exit(1) + } + Expect(teststruct["default"][0]["type"]).To(Equal("insecureAcceptAnything")) + }) +}) diff --git a/test/install/Dockerfile.Fedora b/test/install/Dockerfile.Fedora index 188e60328..3a7b472de 100644 --- a/test/install/Dockerfile.Fedora +++ b/test/install/Dockerfile.Fedora @@ -1,3 +1,3 @@ -FROM registry.fedoraproject.org/fedora:28 +FROM registry.fedoraproject.org/fedora:29 -RUN dnf install -y rpms/noarch/* rpms/x86_64/*
\ No newline at end of file +RUN dnf install -y rpms/noarch/* rpms/x86_64/* |