diff --git a/.travis.yml b/.travis.yml index 9114fec81..f9f2fe524 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ go: - tip dist: trusty sudo: required +services: + - docker before_install: - sudo add-apt-repository -y ppa:duggan/bats - sudo apt-get -qq update diff --git a/buildah.go b/buildah.go index 1f11404e8..c124b445f 100644 --- a/buildah.go +++ b/buildah.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" + "github.com/containers/image/types" "github.com/containers/storage" "github.com/containers/storage/pkg/ioutils" "github.com/opencontainers/image-spec/specs-go/v1" @@ -122,6 +123,9 @@ type BuilderOptions struct { // ReportWriter is an io.Writer which will be used to log the reading // of the source image from a registry, if we end up pulling the image. ReportWriter io.Writer + // github.com/containers/image/types SystemContext to hold credentials + // and other authentication/authorization information. + SystemContext *types.SystemContext } // ImportOptions are used to initialize a Builder from an existing container diff --git a/cmd/buildah/commit.go b/cmd/buildah/commit.go index 63211f013..811aeec76 100644 --- a/cmd/buildah/commit.go +++ b/cmd/buildah/commit.go @@ -19,6 +19,11 @@ var ( Name: "disable-compression, D", Usage: "don't compress layers", }, + cli.StringFlag{ + Name: "creds", + Value: "", + Usage: "use `username[:password]` for accessing the registry", + }, cli.StringFlag{ Name: "signature-policy", Usage: "`pathname` of signature policy file (not usually used)", diff --git a/cmd/buildah/common.go b/cmd/buildah/common.go index 5262dea11..12b2d3154 100644 --- a/cmd/buildah/common.go +++ b/cmd/buildah/common.go @@ -2,9 +2,12 @@ package main import ( "os" + "strings" + "syscall" "time" is "github.com/containers/image/storage" + "github.com/containers/image/types" "github.com/containers/storage" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -108,3 +111,50 @@ func getDateAndDigestAndSize(image storage.Image, store storage.Store) (time.Tim } return created, manifestDigest, imgSize, err } + +// systemContextFromOptions returns a SystemContext populated with values +// per the input parameters provided by the caller for the use in authentication. +func systemContextFromOptions(c *cli.Context) (*types.SystemContext, error) { + ctx := &types.SystemContext{ + DockerCertPath: c.String("cert-dir"), + } + if c.IsSet("tls-verify") { + ctx.DockerInsecureSkipTLSVerify = !c.BoolT("tls-verify") + } + if c.IsSet("creds") { + var err error + ctx.DockerAuthConfig, err = getDockerAuth(c.String("creds")) + if err != nil { + return nil, err + } + } + if c.IsSet("signature-policy") { + ctx.SignaturePolicyPath = c.String("signature-policy") + } + return ctx, nil +} + +func parseCreds(creds string) (string, string, error) { + if creds == "" { + return "", "", errors.Wrapf(syscall.EINVAL, "credentials can't be empty") + } + up := strings.SplitN(creds, ":", 2) + if len(up) == 1 { + return up[0], "", nil + } + if up[0] == "" { + return "", "", errors.Wrapf(syscall.EINVAL, "username can't be empty") + } + return up[0], up[1], nil +} + +func getDockerAuth(creds string) (*types.DockerAuthConfig, error) { + username, password, err := parseCreds(creds) + if err != nil { + return nil, err + } + return &types.DockerAuthConfig{ + Username: username, + Password: password, + }, nil +} diff --git a/cmd/buildah/from.go b/cmd/buildah/from.go index 7e1c1261f..6af99ad93 100644 --- a/cmd/buildah/from.go +++ b/cmd/buildah/from.go @@ -35,6 +35,20 @@ var ( Usage: "`prefix` to prepend to the image name in order to pull the image", Value: DefaultTransport, }, + cli.StringFlag{ + Name: "cert-dir", + Value: "", + Usage: "use certificates at the specified path to access the registry", + }, + cli.StringFlag{ + Name: "creds", + Value: "", + Usage: "use `username[:password]` for accessing the registry", + }, + cli.StringFlag{ + Name: "tls-verify", + Usage: "Require HTTPS and verify certificates when accessing the registry", + }, cli.StringFlag{ Name: "signature-policy", Usage: "`pathname` of signature policy file (not usually used)", @@ -65,12 +79,18 @@ func fromCmd(c *cli.Context) error { if len(args) > 1 { return errors.Errorf("too many arguments specified") } - image := args[0] + image := args[0] transport := DefaultTransport if c.IsSet("transport") { transport = c.String("transport") } + + systemContext, err := systemContextFromOptions(c) + if err != nil { + return errors.Errorf("error building system context [%v]", err) + } + pull := true if c.IsSet("pull") { pull = c.BoolT("pull") @@ -113,6 +133,7 @@ func fromCmd(c *cli.Context) error { PullPolicy: pullPolicy, Transport: transport, SignaturePolicyPath: signaturePolicy, + SystemContext: systemContext, } if !quiet { options.ReportWriter = os.Stderr diff --git a/commit.go b/commit.go index a8d3c5643..eba09664a 100644 --- a/commit.go +++ b/commit.go @@ -249,7 +249,8 @@ func (b *Builder) Commit(dest types.ImageReference, options CommitOptions) error } if exporting { // Copy everything. - err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter)) + // TODO: add credsContext + err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, nil)) if err != nil { return errors.Wrapf(err, "error copying layers and metadata") } @@ -321,7 +322,8 @@ func Push(image string, dest types.ImageReference, options PushOptions) error { return errors.Wrapf(err, "error recomputing layer digests and building metadata") } // Copy everything. - err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter)) + // TODO: add credsContext + err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, nil)) if err != nil { return errors.Wrapf(err, "error copying layers and metadata") } diff --git a/common.go b/common.go index 80b918694..21987ab32 100644 --- a/common.go +++ b/common.go @@ -7,9 +7,11 @@ import ( "github.com/containers/image/types" ) -func getCopyOptions(reportWriter io.Writer) *cp.Options { +func getCopyOptions(reportWriter io.Writer, sourceSystemContext *types.SystemContext, destinationSystemContext *types.SystemContext) *cp.Options { return &cp.Options{ - ReportWriter: reportWriter, + ReportWriter: reportWriter, + SourceCtx: sourceSystemContext, + DestinationCtx: destinationSystemContext, } } diff --git a/contrib/completions/bash/buildah b/contrib/completions/bash/buildah index 1e60bab76..2756828ed 100644 --- a/contrib/completions/bash/buildah +++ b/contrib/completions/bash/buildah @@ -618,9 +618,12 @@ return 1 " local options_with_args=" + --cert-dir + --creds --name --transport --signature-policy + --tls-verify " diff --git a/docs/buildah-from.md b/docs/buildah-from.md index 244282525..d09d5e512 100644 --- a/docs/buildah-from.md +++ b/docs/buildah-from.md @@ -15,6 +15,14 @@ The container ID of the container that was created. On error, -1 is returned an ## OPTIONS +**--cert-dir** *path* + +Use certificates at *path* (*.crt, *.cert, *.key) to connect to the registry + +**--creds** *creds* + +The username and password to use to authenticate with the registry if required. + **--name** *name* A *name* for the working container @@ -41,6 +49,10 @@ 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** *bool-value* + +Require HTTPS and verify certificates when talking to container registries (defaults to true) + **--quiet** If an image needs to be pulled from the registry, suppress progress output. @@ -55,5 +67,7 @@ buildah from imagename --signature-policy /etc/containers/policy.json buildah from imagename --pull-always --transport "docker://myregistry.example.com/" --name "mycontainer" +buildah from myregistry/myrepository/imagename:imagetag --creds=myusername:mypassword + ## SEE ALSO buildah(1) diff --git a/pull.go b/pull.go index 54e4ee514..18bb7a6d7 100644 --- a/pull.go +++ b/pull.go @@ -109,6 +109,6 @@ func pullImage(store storage.Store, options BuilderOptions, sc *types.SystemCont logrus.Debugf("copying %q to %q", spec, name) - err = cp.Image(policyContext, destRef, srcRef, getCopyOptions(options.ReportWriter)) + err = cp.Image(policyContext, destRef, srcRef, getCopyOptions(options.ReportWriter, options.SystemContext, nil)) return destRef, err } diff --git a/tests/from.bats b/tests/from.bats index 44c98d839..e7a10634b 100644 --- a/tests/from.bats +++ b/tests/from.bats @@ -34,3 +34,81 @@ load helpers buildah rmi ${elsewhere} [ "$cid" = `basename ${elsewhere}`-working-container ] } + +@test "from-authenticate-cert" { + + mkdir -p ${TESTDIR}/auth + # Create certifcate via openssl + openssl req -newkey rsa:4096 -nodes -sha256 -keyout ${TESTDIR}/auth/domain.key -x509 -days 2 -out ${TESTDIR}/auth/domain.crt -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost" + # Skopeo and buildah both require *.cert file + cp ${TESTDIR}/auth/domain.crt ${TESTDIR}/auth/domain.cert + + # Create a private registry that uses certificate and creds file +# docker run -d -p 5000:5000 --name registry -v ${TESTDIR}/auth:${TESTDIR}/auth:Z -e REGISTRY_HTTP_TLS_CERTIFICATE=${TESTDIR}/auth/domain.crt -e REGISTRY_HTTP_TLS_KEY=${TESTDIR}/auth/domain.key registry:2 + + # When more buildah auth is in place convert the below. +# docker pull alpine +# docker tag alpine localhost:5000/my-alpine +# docker push localhost:5000/my-alpine + +# ctrid=$(buildah from localhost:5000/my-alpine --cert-dir ${TESTDIR}/auth) +# buildah rm $ctrid +# buildah rmi -f $(buildah --debug=false images -q) + + # This should work +# ctrid=$(buildah from localhost:5000/my-alpine --cert-dir ${TESTDIR}/auth --tls-verify true) + + rm -rf ${TESTDIR}/auth + + # This should fail + run ctrid=$(buildah from localhost:5000/my-alpine --cert-dir ${TESTDIR}/auth --tls-verify true) + [ "$status" -ne 0 ] + + # Clean up +# docker rm -f $(docker ps --all -q) +# docker rmi -f localhost:5000/my-alpine +# docker rmi -f $(docker images -q) +# buildah rm $ctrid +# buildah rmi -f $(buildah --debug=false images -q) +} + +@test "from-authenticate-cert-and-creds" { + + mkdir -p ${TESTDIR}/auth + # Create creds and store in ${TESTDIR}/auth/htpasswd +# docker run --entrypoint htpasswd registry:2 -Bbn testuser testpassword > ${TESTDIR}/auth/htpasswd + # Create certifcate via openssl + openssl req -newkey rsa:4096 -nodes -sha256 -keyout ${TESTDIR}/auth/domain.key -x509 -days 2 -out ${TESTDIR}/auth/domain.crt -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost" + # Skopeo and buildah both require *.cert file + cp ${TESTDIR}/auth/domain.crt ${TESTDIR}/auth/domain.cert + + # Create a private registry that uses certificate and creds file +# docker run -d -p 5000:5000 --name registry -v ${TESTDIR}/auth:${TESTDIR}/auth:Z -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=${TESTDIR}/auth/htpasswd -e REGISTRY_HTTP_TLS_CERTIFICATE=${TESTDIR}/auth/domain.crt -e REGISTRY_HTTP_TLS_KEY=${TESTDIR}/auth/domain.key registry:2 + + # When more buildah auth is in place convert the below. +# docker pull alpine +# docker login localhost:5000 --username testuser --password testpassword +# docker tag alpine localhost:5000/my-alpine +# docker push localhost:5000/my-alpine + +# ctrid=$(buildah from localhost:5000/my-alpine --cert-dir ${TESTDIR}/auth) +# buildah rm $ctrid +# buildah rmi -f $(buildah --debug=false images -q) + +# docker logout localhost:5000 + + # This should fail + run ctrid=$(buildah from localhost:5000/my-alpine --cert-dir ${TESTDIR}/auth --tls-verify true) + [ "$status" -ne 0 ] + + # This should work +# ctrid=$(buildah from localhost:5000/my-alpine --cert-dir ${TESTDIR}/auth --tls-verify true --creds=testuser:testpassword) + + # Clean up + rm -rf ${TESTDIR}/auth +# docker rm -f $(docker ps --all -q) +# docker rmi -f localhost:5000/my-alpine +# docker rmi -f $(docker images -q) +# buildah rm $ctrid +# buildah rmi -f $(buildah --debug=false images -q) +}