diff --git a/cmd/oras/pull.go b/cmd/oras/pull.go index 84f0356ae..ab60f792d 100644 --- a/cmd/oras/pull.go +++ b/cmd/oras/pull.go @@ -26,10 +26,12 @@ type pullOptions struct { output string verbose bool - debug bool - configs []string - username string - password string + debug bool + configs []string + username string + password string + insecure bool + plainHTTP bool } func pullCmd() *cobra.Command { @@ -47,6 +49,12 @@ Example - Pull only files with the custom "application/vnd.me.hi" media type: Example - Pull all files, any media type: oras pull localhost:5000/hello:latest -a + +Example - Pull files from the insecure registry: + oras pull localhost:5000/hello:latest --insecure + +Example - Pull files from the HTTP registry: + oras pull localhost:5000/hello:latest --plain-http `, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { @@ -66,6 +74,8 @@ Example - Pull all files, any media type: cmd.Flags().StringArrayVarP(&opts.configs, "config", "c", nil, "auth config path") cmd.Flags().StringVarP(&opts.username, "username", "u", "", "registry username") cmd.Flags().StringVarP(&opts.password, "password", "p", "", "registry password") + cmd.Flags().BoolVarP(&opts.insecure, "insecure", "", false, "allow connections to SSL registry without certs") + cmd.Flags().BoolVarP(&opts.plainHTTP, "plain-http", "", false, "use plain http and not https") return cmd } @@ -82,7 +92,7 @@ func runPull(opts pullOptions) error { opts.allowedMediaTypes = []string{content.DefaultBlobMediaType, content.DefaultBlobDirMediaType} } - resolver := newResolver(opts.username, opts.password, opts.configs...) + resolver := newResolver(opts.username, opts.password, opts.insecure, opts.plainHTTP, opts.configs...) store := content.NewFileStore(opts.output) defer store.Close() store.DisableOverwrite = opts.keepOldFiles diff --git a/cmd/oras/push.go b/cmd/oras/push.go index 778c0012d..09b25a757 100644 --- a/cmd/oras/push.go +++ b/cmd/oras/push.go @@ -31,10 +31,12 @@ type pushOptions struct { pathValidationDisabled bool verbose bool - debug bool - configs []string - username string - password string + debug bool + configs []string + username string + password string + insecure bool + plainHTTP bool } func pushCmd() *cobra.Command { @@ -55,6 +57,12 @@ Example - Push multiple files with different media types: Example - Push file "hi.txt" with the custom manifest config "config.json" of the custom "application/vnd.me.config" media type: oras push --manifest-config config.json:application/vnd.me.config localhost:5000/hello:latest hi.txt + +Example - Push file to the insecure registry: + oras push localhost:5000/hello:latest hi.txt --insecure + +Example - Push file to the HTTP registry: + oras push localhost:5000/hello:latest hi.txt --plain-http `, Args: cobra.MinimumNArgs(2), RunE: func(cmd *cobra.Command, args []string) error { @@ -72,6 +80,8 @@ Example - Push file "hi.txt" with the custom manifest config "config.json" of th cmd.Flags().StringArrayVarP(&opts.configs, "config", "c", nil, "auth config path") cmd.Flags().StringVarP(&opts.username, "username", "u", "", "registry username") cmd.Flags().StringVarP(&opts.password, "password", "p", "", "registry password") + cmd.Flags().BoolVarP(&opts.insecure, "insecure", "", false, "allow connections to SSL registry without certs") + cmd.Flags().BoolVarP(&opts.plainHTTP, "plain-http", "", false, "use plain http and not https") return cmd } @@ -119,7 +129,7 @@ func runPush(opts pushOptions) error { } // ready to push - resolver := newResolver(opts.username, opts.password, opts.configs...) + resolver := newResolver(opts.username, opts.password, opts.insecure, opts.plainHTTP, opts.configs...) pushOpts = append(pushOpts, oras.WithPushBaseHandler(pushStatusTrack())) desc, err := oras.Push(ctx, resolver, opts.targetRef, store, files, pushOpts...) if err != nil { diff --git a/cmd/oras/resolver.go b/cmd/oras/resolver.go index fdefff01a..9ee56be48 100644 --- a/cmd/oras/resolver.go +++ b/cmd/oras/resolver.go @@ -2,7 +2,9 @@ package main import ( "context" + "crypto/tls" "fmt" + "net/http" "os" auth "github.com/deislabs/oras/pkg/auth/docker" @@ -11,22 +13,37 @@ import ( "github.com/containerd/containerd/remotes/docker" ) -func newResolver(username, password string, configs ...string) remotes.Resolver { - if username != "" || password != "" { - return docker.NewResolver(docker.ResolverOptions{ - Credentials: func(hostName string) (string, string, error) { - return username, password, nil +func newResolver(username, password string, insecure bool, plainHTTP bool, configs ...string) remotes.Resolver { + + opts := docker.ResolverOptions{ + PlainHTTP: plainHTTP, + } + + client := http.DefaultClient + if insecure { + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, }, - }) + } + } + opts.Client = client + + + if username != "" || password != "" { + opts.Credentials = func(hostName string) (string, string, error) { + return username, password, nil + } + return docker.NewResolver(opts) } cli, err := auth.NewClient(configs...) if err != nil { fmt.Fprintf(os.Stderr, "WARNING: Error loading auth file: %v\n", err) } - resolver, err := cli.Resolver(context.Background()) + resolver, err := cli.Resolver(context.Background(), client, plainHTTP) if err != nil { fmt.Fprintf(os.Stderr, "WARNING: Error loading resolver: %v\n", err) - resolver = docker.NewResolver(docker.ResolverOptions{}) + resolver = docker.NewResolver(opts) } return resolver } diff --git a/implementors.md b/implementors.md index c5d66af96..1087f3347 100644 --- a/implementors.md +++ b/implementors.md @@ -73,30 +73,106 @@ To login to the registry without a certificate, a self-signed certificate, or an ```sh htpasswd -cB -b auth.htpasswd myuser mypass ``` + +- Generate your self-signed certificates: + + ```sh + $ mkdir -p certs + $ openssl req \ + -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key \ + -x509 -days 365 -out certs/domain.crt + ``` - Start a registry using that file for auth and listen the `0.0.0.0` address: ```sh - docker run -it --rm -p 8443:443 \ + docker run -it --rm -p 5000:5000 \ + -v `pwd`/certs:/certs \ -v $(pwd)/auth.htpasswd:/etc/docker/registry/auth.htpasswd \ -e REGISTRY_AUTH="{htpasswd: {realm: localhost, path: /etc/docker/registry/auth.htpasswd}}" \ - -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \ + -e REGISTRY_HTTP_ADDR=0.0.0.0:5000 \ + -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \ + -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \ registry ``` - In a new window, login with `oras` using the ip address not localhost: ```sh - oras login -u myuser -p mypass --insecure :8443 + oras login -u myuser -p mypass --insecure :5000 ``` -You will notice a new entry for `:8443` appear in `~/.docker/config.json`. +You will notice a new entry for `:5000` appear in `~/.docker/config.json`. -To remove the entry from the credentials file, use `oras logout`: +Then you can pull files from the registry or push files to the registry. -```sh -oras logout :8443 -``` +- To push single file to this registry: + + ```sh + oras push :5000/library/hello:latest hi.txt --insecure + ``` + +- To pull files from this registry: + + ```sh + oras pull :5000/library/hello:latest --insecure + ``` + +- To remove the entry from the credentials file, use `oras logout`: + + ```sh + oras logout :5000 + ``` + +#### Using an plain HTTP Docker registry + +To pull or push the HTTP Docker registry. `oras` support `--plain-http` flag to pull or push. + +The `--plain-http` flag mean that you want to use http instead of https to connect the Docker registry. + +- Create a valid htpasswd file (must use `-B` for bcrypt): + + ```sh + htpasswd -cB -b auth.htpasswd myuser mypass + ``` + +- Start a registry using that file for auth and listen the `0.0.0.0` address: + + ```sh + docker run -it --rm -p 5000:5000 \ + -v $(pwd)/auth.htpasswd:/etc/docker/registry/auth.htpasswd \ + -e REGISTRY_AUTH="{htpasswd: {realm: localhost, path: /etc/docker/registry/auth.htpasswd}}" \ + -e REGISTRY_HTTP_ADDR=0.0.0.0:5000 \ + registry + ``` + +- In a new window, login with `oras` using the ip address not localhost: + + ```sh + oras login -u myuser -p mypass --insecure :5000 + ``` + +You will notice a new entry for `:5000` appear in `~/.docker/config.json`. + +Then you can pull files from the registry or push files to the registry. + +- To push single file to this registry: + + ```sh + oras push :5000/library/hello:latest hi.txt --plain-http + ``` + +- To pull files from this registry: + + ```sh + oras pull :5000/library/hello:latest --plain-http + ``` + +- To remove the entry from the credentials file, use `oras logout`: + + ```sh + oras logout :5000 + ``` ### [Azure Container Registry (ACR)](https://aka.ms/acr) diff --git a/pkg/auth/client.go b/pkg/auth/client.go index b36d4d019..8fa1d6727 100644 --- a/pkg/auth/client.go +++ b/pkg/auth/client.go @@ -3,6 +3,7 @@ package auth import ( "context" "errors" + "net/http" "github.com/containerd/containerd/remotes" ) @@ -19,5 +20,5 @@ type Client interface { // Logout logs out from a remote server identified by the hostname. Logout(ctx context.Context, hostname string) error // Resolver returns a new authenticated resolver. - Resolver(ctx context.Context) (remotes.Resolver, error) + Resolver(ctx context.Context, client *http.Client, plainHTTP bool) (remotes.Resolver, error) } diff --git a/pkg/auth/docker/resolver.go b/pkg/auth/docker/resolver.go index b72d7e224..51358be60 100644 --- a/pkg/auth/docker/resolver.go +++ b/pkg/auth/docker/resolver.go @@ -2,6 +2,7 @@ package docker import ( "context" + "net/http" "github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes/docker" @@ -10,9 +11,11 @@ import ( ) // Resolver returns a new authenticated resolver. -func (c *Client) Resolver(_ context.Context) (remotes.Resolver, error) { +func (c *Client) Resolver(_ context.Context, client *http.Client, plainHTTP bool) (remotes.Resolver, error) { return docker.NewResolver(docker.ResolverOptions{ Credentials: c.Credential, + Client: client, + PlainHTTP: plainHTTP, }), nil }