Merge pull request #810 from umohnani8/kpod_login
Add "kpod login" command
This commit is contained in:
commit
3991a0531c
34 changed files with 1697 additions and 210 deletions
110
cmd/kpod/login.go
Normal file
110
cmd/kpod/login.go
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containers/image/docker"
|
||||||
|
"github.com/containers/image/pkg/docker/config"
|
||||||
|
"github.com/kubernetes-incubator/cri-o/libpod/common"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loginFlags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "password, p",
|
||||||
|
Usage: "Password for registry",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "username, u",
|
||||||
|
Usage: "Username for registry",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "authfile",
|
||||||
|
Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
loginDescription = "Login to a container registry on a specified server."
|
||||||
|
loginCommand = cli.Command{
|
||||||
|
Name: "login",
|
||||||
|
Usage: "login to a container registry",
|
||||||
|
Description: loginDescription,
|
||||||
|
Flags: loginFlags,
|
||||||
|
Action: loginCmd,
|
||||||
|
ArgsUsage: "REGISTRY",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// loginCmd uses the authentication package to store a user's authenticated credentials
|
||||||
|
// in an auth.json file for future use
|
||||||
|
func loginCmd(c *cli.Context) error {
|
||||||
|
args := c.Args()
|
||||||
|
if len(args) > 1 {
|
||||||
|
return errors.Errorf("too many arguments, login takes only 1 argument")
|
||||||
|
}
|
||||||
|
if len(args) == 0 {
|
||||||
|
return errors.Errorf("registry must be given")
|
||||||
|
}
|
||||||
|
var server string
|
||||||
|
if len(args) == 1 {
|
||||||
|
server = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
sc := common.GetSystemContext("", c.String("authfile"))
|
||||||
|
|
||||||
|
// username of user logged in to server (if one exists)
|
||||||
|
userFromAuthFile := config.GetUserLoggedIn(sc, server)
|
||||||
|
username, password, err := getUserAndPass(c.String("username"), c.String("password"), userFromAuthFile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error getting username and password")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = docker.CheckAuth(context.TODO(), sc, username, password, server); err == nil {
|
||||||
|
if err := config.SetAuthentication(sc, server, username, password); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
fmt.Println("Login Succeeded!")
|
||||||
|
return nil
|
||||||
|
case docker.ErrUnauthorizedForCredentials:
|
||||||
|
return errors.Errorf("error logging into %q: invalid username/password\n", server)
|
||||||
|
default:
|
||||||
|
return errors.Wrapf(err, "error authenticating creds for %q", server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUserAndPass gets the username and password from STDIN if not given
|
||||||
|
// using the -u and -p flags
|
||||||
|
func getUserAndPass(username, password, userFromAuthFile string) (string, string, error) {
|
||||||
|
var err error
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
if username == "" {
|
||||||
|
if userFromAuthFile != "" {
|
||||||
|
fmt.Printf("Username (%s): ", userFromAuthFile)
|
||||||
|
} else {
|
||||||
|
fmt.Print("Username: ")
|
||||||
|
}
|
||||||
|
username, err = reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", "", errors.Wrapf(err, "error reading username")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if password == "" {
|
||||||
|
fmt.Print("Password: ")
|
||||||
|
pass, err := terminal.ReadPassword(0)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", errors.Wrapf(err, "error reading password")
|
||||||
|
}
|
||||||
|
password = string(pass)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(username), password, err
|
||||||
|
}
|
66
cmd/kpod/logout.go
Normal file
66
cmd/kpod/logout.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/image/pkg/docker/config"
|
||||||
|
"github.com/kubernetes-incubator/cri-o/libpod/common"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logoutFlags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "authfile",
|
||||||
|
Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "all, a",
|
||||||
|
Usage: "Remove the cached credentials for all registries in the auth file",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
logoutDescription = "Remove the cached username and password for the registry."
|
||||||
|
logoutCommand = cli.Command{
|
||||||
|
Name: "logout",
|
||||||
|
Usage: "logout of a container registry",
|
||||||
|
Description: logoutDescription,
|
||||||
|
Flags: logoutFlags,
|
||||||
|
Action: logoutCmd,
|
||||||
|
ArgsUsage: "REGISTRY",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// logoutCmd uses the authentication package to remove the authenticated of a registry
|
||||||
|
// stored in the auth.json file
|
||||||
|
func logoutCmd(c *cli.Context) error {
|
||||||
|
args := c.Args()
|
||||||
|
if len(args) > 1 {
|
||||||
|
return errors.Errorf("too many arguments, logout takes only 1 argument")
|
||||||
|
}
|
||||||
|
var server string
|
||||||
|
if len(args) == 1 {
|
||||||
|
server = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
sc := common.GetSystemContext("", c.String("authfile"))
|
||||||
|
|
||||||
|
if c.Bool("all") {
|
||||||
|
if err := config.RemoveAllAuthentication(sc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("Remove login credentials for all registries")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.RemoveAuthentication(sc, server)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
fmt.Printf("Remove login credentials for %s\n", server)
|
||||||
|
return nil
|
||||||
|
case config.ErrNotLoggedIn:
|
||||||
|
return errors.Errorf("Not logged into %s\n", server)
|
||||||
|
default:
|
||||||
|
return errors.Wrapf(err, "error logging out of %q", server)
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,6 +39,8 @@ func main() {
|
||||||
inspectCommand,
|
inspectCommand,
|
||||||
killCommand,
|
killCommand,
|
||||||
loadCommand,
|
loadCommand,
|
||||||
|
loginCommand,
|
||||||
|
logoutCommand,
|
||||||
logsCommand,
|
logsCommand,
|
||||||
mountCommand,
|
mountCommand,
|
||||||
pauseCommand,
|
pauseCommand,
|
||||||
|
|
|
@ -431,6 +431,34 @@ _kpod_load() {
|
||||||
_complete_ "$options_with_args" "$boolean_options"
|
_complete_ "$options_with_args" "$boolean_options"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_kpod_login() {
|
||||||
|
local options_with_args="
|
||||||
|
--username
|
||||||
|
-u
|
||||||
|
--password
|
||||||
|
-p
|
||||||
|
--authfile
|
||||||
|
"
|
||||||
|
local boolean_options="
|
||||||
|
--help
|
||||||
|
-h
|
||||||
|
"
|
||||||
|
_complete_ "$options_with_args" "$boolean_options"
|
||||||
|
}
|
||||||
|
|
||||||
|
_kpod_logout() {
|
||||||
|
local options_with_args="
|
||||||
|
--authfile
|
||||||
|
"
|
||||||
|
local boolean_options="
|
||||||
|
--all
|
||||||
|
-a
|
||||||
|
--help
|
||||||
|
-h
|
||||||
|
"
|
||||||
|
_complete_ "$options_with_args" "$boolean_options"
|
||||||
|
}
|
||||||
|
|
||||||
_kpod_kpod() {
|
_kpod_kpod() {
|
||||||
local options_with_args="
|
local options_with_args="
|
||||||
--config -c
|
--config -c
|
||||||
|
@ -453,6 +481,8 @@ _kpod_kpod() {
|
||||||
inspect
|
inspect
|
||||||
kill
|
kill
|
||||||
load
|
load
|
||||||
|
login
|
||||||
|
logout
|
||||||
logs
|
logs
|
||||||
mount
|
mount
|
||||||
pause
|
pause
|
||||||
|
|
65
docs/kpod-login.1.md
Normal file
65
docs/kpod-login.1.md
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
% kpod(1) kpod-login - Simple tool to login to a registry server
|
||||||
|
% Urvashi Mohnani
|
||||||
|
# kpod-login "1" "August 2017" "kpod"
|
||||||
|
|
||||||
|
## NAME
|
||||||
|
kpod-login - Login to a container registry
|
||||||
|
|
||||||
|
## SYNOPSIS
|
||||||
|
**kpod login**
|
||||||
|
[**--help**|**-h**]
|
||||||
|
[**--authfile**]
|
||||||
|
[**--user**|**-u**]
|
||||||
|
[**--password**|**-p**]
|
||||||
|
**REGISTRY**
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
**kpod login** logs into a specified registry server with the correct username
|
||||||
|
and password. **kpod login** reads in the username and password from STDIN.
|
||||||
|
The username and password can also be set using the **username** and **password** flags.
|
||||||
|
The path of the authentication file can be specified by the user by setting the **authfile**
|
||||||
|
flag. The default path used is **${XDG\_RUNTIME_DIR}/containers/auth.json**.
|
||||||
|
|
||||||
|
**kpod [GLOBAL OPTIONS]**
|
||||||
|
|
||||||
|
**kpod login [GLOBAL OPTIONS]**
|
||||||
|
|
||||||
|
**kpod login [OPTIONS] REGISTRY [GLOBAL OPTIONS]**
|
||||||
|
|
||||||
|
## OPTIONS
|
||||||
|
|
||||||
|
**--password, -p**
|
||||||
|
Password for registry
|
||||||
|
|
||||||
|
**--username, -u**
|
||||||
|
Username for registry
|
||||||
|
|
||||||
|
**--authfile**
|
||||||
|
Path of the authentication file. Default is ${XDG_\RUNTIME\_DIR}/containers/auth.json
|
||||||
|
|
||||||
|
## EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
# kpod login docker.io
|
||||||
|
Username: umohnani
|
||||||
|
Password:
|
||||||
|
Login Succeeded!
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# kpod login -u testuser -p testpassword localhost:5000
|
||||||
|
Login Succeeded!
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# kpod login --authfile authdir/myauths.json docker.io
|
||||||
|
Username: umohnani
|
||||||
|
Password:
|
||||||
|
Login Succeeded!
|
||||||
|
```
|
||||||
|
|
||||||
|
## SEE ALSO
|
||||||
|
kpod(1), kpod-logout(1), crio(8), crio.conf(5)
|
||||||
|
|
||||||
|
## HISTORY
|
||||||
|
August 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
56
docs/kpod-logout.1.md
Normal file
56
docs/kpod-logout.1.md
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
% kpod(1) kpod-logout - Simple tool to logout of a registry server
|
||||||
|
% Urvashi Mohnani
|
||||||
|
# kpod-logout "1" "August 2017" "kpod"
|
||||||
|
|
||||||
|
## NAME
|
||||||
|
kpod-logout - Logout of a container registry
|
||||||
|
|
||||||
|
## SYNOPSIS
|
||||||
|
**kpod logout**
|
||||||
|
[**--help**|**-h**]
|
||||||
|
[**--authfile**]
|
||||||
|
[**--all**|**-a**]
|
||||||
|
**REGISTRY**
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
**kpod logout** logs out of a specified registry server by deleting the cached credentials
|
||||||
|
stored in the **auth.json** file. The path of the authentication file can be overrriden by the user by setting the **authfile** flag.
|
||||||
|
The default path used is **${XDG\_RUNTIME_DIR}/containers/auth.json**.
|
||||||
|
All the cached credentials can be removed by setting the **all** flag.
|
||||||
|
|
||||||
|
**kpod [GLOBAL OPTIONS]**
|
||||||
|
|
||||||
|
**kpod logout [GLOBAL OPTIONS]**
|
||||||
|
|
||||||
|
**kpod logout [OPTIONS] REGISTRY [GLOBAL OPTIONS]**
|
||||||
|
|
||||||
|
## OPTIONS
|
||||||
|
|
||||||
|
**--authfile**
|
||||||
|
Path of the authentication file. Default is ${XDG_\RUNTIME\_DIR}/containers/auth.json
|
||||||
|
|
||||||
|
**--all, -a**
|
||||||
|
Remove the cached credentials for all registries in the auth file
|
||||||
|
|
||||||
|
## EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
# kpod logout docker.io
|
||||||
|
Remove login credentials for https://registry-1.docker.io/v2/
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# kpod logout --authfile authdir/myauths.json docker.io
|
||||||
|
Remove login credentials for https://registry-1.docker.io/v2/
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# kpod logout --all
|
||||||
|
Remove login credentials for all registries
|
||||||
|
```
|
||||||
|
|
||||||
|
## SEE ALSO
|
||||||
|
kpod(1), kpod-login(1), crio(8), crio.conf(5)
|
||||||
|
|
||||||
|
## HISTORY
|
||||||
|
August 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -36,11 +36,12 @@ func GetCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDocke
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSystemContext Constructs a new containers/image/types.SystemContext{} struct from the given signaturePolicy path
|
// GetSystemContext Constructs a new containers/image/types.SystemContext{} struct from the given signaturePolicy path
|
||||||
func GetSystemContext(signaturePolicyPath string) *types.SystemContext {
|
func GetSystemContext(signaturePolicyPath, authFilePath string) *types.SystemContext {
|
||||||
sc := &types.SystemContext{}
|
sc := &types.SystemContext{}
|
||||||
if signaturePolicyPath != "" {
|
if signaturePolicyPath != "" {
|
||||||
sc.SignaturePolicyPath = signaturePolicyPath
|
sc.SignaturePolicyPath = signaturePolicyPath
|
||||||
}
|
}
|
||||||
|
sc.AuthFilePath = authFilePath
|
||||||
return sc
|
return sc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -413,7 +413,7 @@ func (r *Runtime) GetImageCopyData(image string) (*CopyData, error) {
|
||||||
return nil, errors.Wrapf(err, "error locating image %q for importing settings", image)
|
return nil, errors.Wrapf(err, "error locating image %q for importing settings", image)
|
||||||
}
|
}
|
||||||
|
|
||||||
systemContext := common.GetSystemContext("")
|
systemContext := common.GetSystemContext("", "")
|
||||||
data, err := r.ImportCopyDataFromImage(systemContext, img.ID, "", "")
|
data, err := r.ImportCopyDataFromImage(systemContext, img.ID, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error reading image")
|
return nil, errors.Wrapf(err, "error reading image")
|
||||||
|
@ -435,7 +435,7 @@ func (r *Runtime) importCopyData(store storage.Store, container, signaturePolicy
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
systemContext := common.GetSystemContext(signaturePolicyPath)
|
systemContext := common.GetSystemContext(signaturePolicyPath, "")
|
||||||
|
|
||||||
data, err := r.ImportCopyDataFromImage(systemContext, c.ImageID, container, c.ID)
|
data, err := r.ImportCopyDataFromImage(systemContext, c.ImageID, container, c.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -103,7 +103,7 @@ func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath st
|
||||||
signaturePolicyPath = r.config.SignaturePolicyPath
|
signaturePolicyPath = r.config.SignaturePolicyPath
|
||||||
}
|
}
|
||||||
|
|
||||||
sc := common.GetSystemContext(signaturePolicyPath)
|
sc := common.GetSystemContext(signaturePolicyPath, "")
|
||||||
|
|
||||||
srcRef, err := alltransports.ParseImageName(imgName)
|
srcRef, err := alltransports.ParseImageName(imgName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,7 +4,7 @@ k8s.io/apimachinery release-1.7 https://github.com/kubernetes/apimachinery
|
||||||
k8s.io/apiserver release-1.7 https://github.com/kubernetes/apiserver
|
k8s.io/apiserver release-1.7 https://github.com/kubernetes/apiserver
|
||||||
#
|
#
|
||||||
github.com/sirupsen/logrus v1.0.0
|
github.com/sirupsen/logrus v1.0.0
|
||||||
github.com/containers/image d17474f39dae1da15ab9ae033d57ebefcf62f77a
|
github.com/containers/image 57b257d128d6075ea3287991ee408d24c7bd2758
|
||||||
github.com/docker/docker-credential-helpers d68f9aeca33f5fd3f08eeae5e9d175edf4e731d1
|
github.com/docker/docker-credential-helpers d68f9aeca33f5fd3f08eeae5e9d175edf4e731d1
|
||||||
github.com/ostreedev/ostree-go master
|
github.com/ostreedev/ostree-go master
|
||||||
github.com/containers/storage 64bf27465d0d1edd89e7a4ce49866fea01145782
|
github.com/containers/storage 64bf27465d0d1edd89e7a4ce49866fea01145782
|
||||||
|
|
2
vendor/github.com/containers/image/copy/copy.go
generated
vendored
2
vendor/github.com/containers/image/copy/copy.go
generated
vendored
|
@ -581,7 +581,7 @@ func (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.Blo
|
||||||
bar.ShowPercent = false
|
bar.ShowPercent = false
|
||||||
bar.Start()
|
bar.Start()
|
||||||
destStream = bar.NewProxyReader(destStream)
|
destStream = bar.NewProxyReader(destStream)
|
||||||
defer fmt.Fprint(ic.reportWriter, "\n")
|
defer bar.Finish()
|
||||||
|
|
||||||
// === Send a copy of the original, uncompressed, stream, to a separate path if necessary.
|
// === Send a copy of the original, uncompressed, stream, to a separate path if necessary.
|
||||||
var originalLayerReader io.Reader // DO NOT USE this other than to drain the input if no other consumer in the pipeline has done so.
|
var originalLayerReader io.Reader // DO NOT USE this other than to drain the input if no other consumer in the pipeline has done so.
|
||||||
|
|
244
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
244
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
|
@ -3,12 +3,10 @@ package docker
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -16,11 +14,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/image/docker/reference"
|
"github.com/containers/image/docker/reference"
|
||||||
|
"github.com/containers/image/pkg/docker/config"
|
||||||
|
"github.com/containers/image/pkg/tlsclientconfig"
|
||||||
"github.com/containers/image/types"
|
"github.com/containers/image/types"
|
||||||
"github.com/containers/storage/pkg/homedir"
|
|
||||||
"github.com/docker/distribution/registry/client"
|
"github.com/docker/distribution/registry/client"
|
||||||
helperclient "github.com/docker/docker-credential-helpers/client"
|
|
||||||
"github.com/docker/go-connections/sockets"
|
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -28,13 +25,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dockerHostname = "docker.io"
|
dockerHostname = "docker.io"
|
||||||
dockerRegistry = "registry-1.docker.io"
|
dockerRegistry = "registry-1.docker.io"
|
||||||
dockerAuthRegistry = "https://index.docker.io/v1/"
|
|
||||||
|
|
||||||
dockerCfg = ".docker"
|
|
||||||
dockerCfgFileName = "config.json"
|
|
||||||
dockerCfgObsolete = ".dockercfg"
|
|
||||||
|
|
||||||
systemPerHostCertDirPath = "/etc/docker/certs.d"
|
systemPerHostCertDirPath = "/etc/docker/certs.d"
|
||||||
|
|
||||||
|
@ -52,9 +44,13 @@ const (
|
||||||
extensionSignatureTypeAtomic = "atomic" // extensionSignature.Type
|
extensionSignatureTypeAtomic = "atomic" // extensionSignature.Type
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrV1NotSupported is returned when we're trying to talk to a
|
var (
|
||||||
// docker V1 registry.
|
// ErrV1NotSupported is returned when we're trying to talk to a
|
||||||
var ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
|
// docker V1 registry.
|
||||||
|
ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
|
||||||
|
// ErrUnauthorizedForCredentials is returned when the status code returned is 401
|
||||||
|
ErrUnauthorizedForCredentials = errors.New("unable to retrieve auth token: invalid username/password")
|
||||||
|
)
|
||||||
|
|
||||||
// extensionSignature and extensionSignatureList come from github.com/openshift/origin/pkg/dockerregistry/server/signaturedispatcher.go:
|
// extensionSignature and extensionSignatureList come from github.com/openshift/origin/pkg/dockerregistry/server/signaturedispatcher.go:
|
||||||
// signature represents a Docker image signature.
|
// signature represents a Docker image signature.
|
||||||
|
@ -113,27 +109,7 @@ func serverDefault() *tls.Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTransport() *http.Transport {
|
// dockerCertDir returns a path to a directory to be consumed by tlsclientconfig.SetupCertificates() depending on ctx and hostPort.
|
||||||
direct := &net.Dialer{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
KeepAlive: 30 * time.Second,
|
|
||||||
DualStack: true,
|
|
||||||
}
|
|
||||||
tr := &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
Dial: direct.Dial,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
// TODO(dmcgowan): Call close idle connections when complete and use keep alive
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
}
|
|
||||||
proxyDialer, err := sockets.DialerFromEnvironment(direct)
|
|
||||||
if err == nil {
|
|
||||||
tr.Dial = proxyDialer.Dial
|
|
||||||
}
|
|
||||||
return tr
|
|
||||||
}
|
|
||||||
|
|
||||||
// dockerCertDir returns a path to a directory to be consumed by setupCertificates() depending on ctx and hostPort.
|
|
||||||
func dockerCertDir(ctx *types.SystemContext, hostPort string) string {
|
func dockerCertDir(ctx *types.SystemContext, hostPort string) string {
|
||||||
if ctx != nil && ctx.DockerCertPath != "" {
|
if ctx != nil && ctx.DockerCertPath != "" {
|
||||||
return ctx.DockerCertPath
|
return ctx.DockerCertPath
|
||||||
|
@ -212,52 +188,84 @@ func hasFile(files []os.FileInfo, name string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
// newDockerClientFromRef returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
||||||
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
|
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
|
||||||
func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
|
func newDockerClientFromRef(ctx *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
|
||||||
registry := reference.Domain(ref.ref)
|
registry := reference.Domain(ref.ref)
|
||||||
if registry == dockerHostname {
|
username, password, err := config.GetAuthentication(ctx, reference.Domain(ref.ref))
|
||||||
registry = dockerRegistry
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error getting username and password")
|
||||||
}
|
}
|
||||||
username, password, err := getAuth(ctx, reference.Domain(ref.ref))
|
sigBase, err := configuredSignatureStorageBase(ctx, ref, write)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tr := newTransport()
|
remoteName := reference.Path(ref.ref)
|
||||||
|
|
||||||
|
return newDockerClientWithDetails(ctx, registry, username, password, actions, sigBase, remoteName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDockerClientWithDetails returns a new dockerClient instance for the given parameters
|
||||||
|
func newDockerClientWithDetails(ctx *types.SystemContext, registry, username, password, actions string, sigBase signatureStorageBase, remoteName string) (*dockerClient, error) {
|
||||||
|
hostName := registry
|
||||||
|
if registry == dockerHostname {
|
||||||
|
registry = dockerRegistry
|
||||||
|
}
|
||||||
|
tr := tlsclientconfig.NewTransport()
|
||||||
tr.TLSClientConfig = serverDefault()
|
tr.TLSClientConfig = serverDefault()
|
||||||
|
|
||||||
// It is undefined whether the host[:port] string for dockerHostname should be dockerHostname or dockerRegistry,
|
// It is undefined whether the host[:port] string for dockerHostname should be dockerHostname or dockerRegistry,
|
||||||
// because docker/docker does not read the certs.d subdirectory at all in that case. We use the user-visible
|
// because docker/docker does not read the certs.d subdirectory at all in that case. We use the user-visible
|
||||||
// dockerHostname here, because it is more symmetrical to read the configuration in that case as well, and because
|
// dockerHostname here, because it is more symmetrical to read the configuration in that case as well, and because
|
||||||
// generally the UI hides the existence of the different dockerRegistry. But note that this behavior is
|
// generally the UI hides the existence of the different dockerRegistry. But note that this behavior is
|
||||||
// undocumented and may change if docker/docker changes.
|
// undocumented and may change if docker/docker changes.
|
||||||
certDir := dockerCertDir(ctx, reference.Domain(ref.ref))
|
certDir := dockerCertDir(ctx, hostName)
|
||||||
if err := setupCertificates(certDir, tr.TLSClientConfig); err != nil {
|
if err := tlsclientconfig.SetupCertificates(certDir, tr.TLSClientConfig); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx != nil && ctx.DockerInsecureSkipTLSVerify {
|
if ctx != nil && ctx.DockerInsecureSkipTLSVerify {
|
||||||
tr.TLSClientConfig.InsecureSkipVerify = true
|
tr.TLSClientConfig.InsecureSkipVerify = true
|
||||||
}
|
}
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
|
|
||||||
sigBase, err := configuredSignatureStorageBase(ctx, ref, write)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &dockerClient{
|
return &dockerClient{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
registry: registry,
|
registry: registry,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
client: client,
|
client: &http.Client{Transport: tr},
|
||||||
signatureBase: sigBase,
|
signatureBase: sigBase,
|
||||||
scope: authScope{
|
scope: authScope{
|
||||||
actions: actions,
|
actions: actions,
|
||||||
remoteName: reference.Path(ref.ref),
|
remoteName: remoteName,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckAuth validates the credentials by attempting to log into the registry
|
||||||
|
// returns an error if an error occcured while making the http request or the status code received was 401
|
||||||
|
func CheckAuth(ctx context.Context, sCtx *types.SystemContext, username, password, registry string) error {
|
||||||
|
newLoginClient, err := newDockerClientWithDetails(sCtx, registry, username, password, "", nil, "")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error creating new docker client")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := newLoginClient.makeRequest(ctx, "GET", "/v2/", nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
return nil
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
return ErrUnauthorizedForCredentials
|
||||||
|
default:
|
||||||
|
return errors.Errorf("error occured with status code %q", resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
|
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
|
||||||
// The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/.
|
// The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/.
|
||||||
func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
||||||
|
@ -329,7 +337,10 @@ func (c *dockerClient) setupRequestAuth(req *http.Request) error {
|
||||||
return errors.Errorf("missing realm in bearer auth challenge")
|
return errors.Errorf("missing realm in bearer auth challenge")
|
||||||
}
|
}
|
||||||
service, _ := challenge.Parameters["service"] // Will be "" if not present
|
service, _ := challenge.Parameters["service"] // Will be "" if not present
|
||||||
scope := fmt.Sprintf("repository:%s:%s", c.scope.remoteName, c.scope.actions)
|
var scope string
|
||||||
|
if c.scope.remoteName != "" && c.scope.actions != "" {
|
||||||
|
scope = fmt.Sprintf("repository:%s:%s", c.scope.remoteName, c.scope.actions)
|
||||||
|
}
|
||||||
token, err := c.getBearerToken(req.Context(), realm, service, scope)
|
token, err := c.getBearerToken(req.Context(), realm, service, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -364,7 +375,7 @@ func (c *dockerClient) getBearerToken(ctx context.Context, realm, service, scope
|
||||||
if c.username != "" && c.password != "" {
|
if c.username != "" && c.password != "" {
|
||||||
authReq.SetBasicAuth(c.username, c.password)
|
authReq.SetBasicAuth(c.username, c.password)
|
||||||
}
|
}
|
||||||
tr := newTransport()
|
tr := tlsclientconfig.NewTransport()
|
||||||
// TODO(runcom): insecure for now to contact the external token service
|
// TODO(runcom): insecure for now to contact the external token service
|
||||||
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
client := &http.Client{Transport: tr}
|
client := &http.Client{Transport: tr}
|
||||||
|
@ -375,7 +386,7 @@ func (c *dockerClient) getBearerToken(ctx context.Context, realm, service, scope
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
switch res.StatusCode {
|
switch res.StatusCode {
|
||||||
case http.StatusUnauthorized:
|
case http.StatusUnauthorized:
|
||||||
return nil, errors.Errorf("unable to retrieve auth token: 401 unauthorized")
|
return nil, ErrUnauthorizedForCredentials
|
||||||
case http.StatusOK:
|
case http.StatusOK:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
@ -399,65 +410,6 @@ func (c *dockerClient) getBearerToken(ctx context.Context, realm, service, scope
|
||||||
return &token, nil
|
return &token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuth(ctx *types.SystemContext, registry string) (string, string, error) {
|
|
||||||
if ctx != nil && ctx.DockerAuthConfig != nil {
|
|
||||||
return ctx.DockerAuthConfig.Username, ctx.DockerAuthConfig.Password, nil
|
|
||||||
}
|
|
||||||
var dockerAuth dockerConfigFile
|
|
||||||
dockerCfgPath := filepath.Join(getDefaultConfigDir(".docker"), dockerCfgFileName)
|
|
||||||
if _, err := os.Stat(dockerCfgPath); err == nil {
|
|
||||||
j, err := ioutil.ReadFile(dockerCfgPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(j, &dockerAuth); err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if os.IsNotExist(err) {
|
|
||||||
// try old config path
|
|
||||||
oldDockerCfgPath := filepath.Join(getDefaultConfigDir(dockerCfgObsolete))
|
|
||||||
if _, err := os.Stat(oldDockerCfgPath); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
return "", "", errors.Wrap(err, oldDockerCfgPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
j, err := ioutil.ReadFile(oldDockerCfgPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(j, &dockerAuth.AuthConfigs); err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if err != nil {
|
|
||||||
return "", "", errors.Wrap(err, dockerCfgPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// First try cred helpers. They should always be normalized.
|
|
||||||
if ch, exists := dockerAuth.CredHelpers[registry]; exists {
|
|
||||||
return getAuthFromCredHelper(ch, registry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// I'm feeling lucky.
|
|
||||||
if c, exists := dockerAuth.AuthConfigs[registry]; exists {
|
|
||||||
return decodeDockerAuth(c.Auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bad luck; let's normalize the entries first
|
|
||||||
registry = normalizeRegistry(registry)
|
|
||||||
normalizedAuths := map[string]dockerAuthConfig{}
|
|
||||||
for k, v := range dockerAuth.AuthConfigs {
|
|
||||||
normalizedAuths[normalizeRegistry(k)] = v
|
|
||||||
}
|
|
||||||
if c, exists := normalizedAuths[registry]; exists {
|
|
||||||
return decodeDockerAuth(c.Auth)
|
|
||||||
}
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectProperties detects various properties of the registry.
|
// detectProperties detects various properties of the registry.
|
||||||
// See the dockerClient documentation for members which are affected by this.
|
// See the dockerClient documentation for members which are affected by this.
|
||||||
func (c *dockerClient) detectProperties(ctx context.Context) error {
|
func (c *dockerClient) detectProperties(ctx context.Context) error {
|
||||||
|
@ -540,67 +492,3 @@ func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerRe
|
||||||
}
|
}
|
||||||
return &parsedBody, nil
|
return &parsedBody, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultConfigDir(confPath string) string {
|
|
||||||
return filepath.Join(homedir.Get(), confPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dockerAuthConfig struct {
|
|
||||||
Auth string `json:"auth,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type dockerConfigFile struct {
|
|
||||||
AuthConfigs map[string]dockerAuthConfig `json:"auths"`
|
|
||||||
CredHelpers map[string]string `json:"credHelpers,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAuthFromCredHelper(credHelper, registry string) (string, string, error) {
|
|
||||||
helperName := fmt.Sprintf("docker-credential-%s", credHelper)
|
|
||||||
p := helperclient.NewShellProgramFunc(helperName)
|
|
||||||
creds, err := helperclient.Get(p, registry)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return creds.Username, creds.Secret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeDockerAuth(s string) (string, string, error) {
|
|
||||||
decoded, err := base64.StdEncoding.DecodeString(s)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
parts := strings.SplitN(string(decoded), ":", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
// if it's invalid just skip, as docker does
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
user := parts[0]
|
|
||||||
password := strings.Trim(parts[1], "\x00")
|
|
||||||
return user, password, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertToHostname converts a registry url which has http|https prepended
|
|
||||||
// to just an hostname.
|
|
||||||
// Copied from github.com/docker/docker/registry/auth.go
|
|
||||||
func convertToHostname(url string) string {
|
|
||||||
stripped := url
|
|
||||||
if strings.HasPrefix(url, "http://") {
|
|
||||||
stripped = strings.TrimPrefix(url, "http://")
|
|
||||||
} else if strings.HasPrefix(url, "https://") {
|
|
||||||
stripped = strings.TrimPrefix(url, "https://")
|
|
||||||
}
|
|
||||||
|
|
||||||
nameParts := strings.SplitN(stripped, "/", 2)
|
|
||||||
|
|
||||||
return nameParts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeRegistry(registry string) string {
|
|
||||||
normalized := convertToHostname(registry)
|
|
||||||
switch normalized {
|
|
||||||
case "registry-1.docker.io", "docker.io":
|
|
||||||
return "index.docker.io"
|
|
||||||
}
|
|
||||||
return normalized
|
|
||||||
}
|
|
||||||
|
|
2
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
2
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
|
@ -34,7 +34,7 @@ type dockerImageDestination struct {
|
||||||
|
|
||||||
// newImageDestination creates a new ImageDestination for the specified image reference.
|
// newImageDestination creates a new ImageDestination for the specified image reference.
|
||||||
func newImageDestination(ctx *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
func newImageDestination(ctx *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
||||||
c, err := newDockerClient(ctx, ref, true, "pull,push")
|
c, err := newDockerClientFromRef(ctx, ref, true, "pull,push")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
5
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
5
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
|
@ -31,7 +31,7 @@ type dockerImageSource struct {
|
||||||
// newImageSource creates a new ImageSource for the specified image reference.
|
// newImageSource creates a new ImageSource for the specified image reference.
|
||||||
// The caller must call .Close() on the returned ImageSource.
|
// The caller must call .Close() on the returned ImageSource.
|
||||||
func newImageSource(ctx *types.SystemContext, ref dockerReference) (*dockerImageSource, error) {
|
func newImageSource(ctx *types.SystemContext, ref dockerReference) (*dockerImageSource, error) {
|
||||||
c, err := newDockerClient(ctx, ref, false, "pull")
|
c, err := newDockerClientFromRef(ctx, ref, false, "pull")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,7 @@ func (s *dockerImageSource) getExternalBlob(urls []string) (io.ReadCloser, int64
|
||||||
logrus.Debug(err)
|
logrus.Debug(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if resp.Body != nil && err == nil {
|
if resp.Body != nil && err == nil {
|
||||||
|
@ -297,7 +298,7 @@ func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context) (
|
||||||
|
|
||||||
// deleteImage deletes the named image from the registry, if supported.
|
// deleteImage deletes the named image from the registry, if supported.
|
||||||
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
||||||
c, err := newDockerClient(ctx, ref, true, "push")
|
c, err := newDockerClientFromRef(ctx, ref, true, "push")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
3
vendor/github.com/containers/image/image/oci.go
generated
vendored
3
vendor/github.com/containers/image/image/oci.go
generated
vendored
|
@ -109,7 +109,7 @@ func (m *manifestOCI1) OCIConfig() (*imgspecv1.Image, error) {
|
||||||
func (m *manifestOCI1) LayerInfos() []types.BlobInfo {
|
func (m *manifestOCI1) LayerInfos() []types.BlobInfo {
|
||||||
blobs := []types.BlobInfo{}
|
blobs := []types.BlobInfo{}
|
||||||
for _, layer := range m.LayersDescriptors {
|
for _, layer := range m.LayersDescriptors {
|
||||||
blobs = append(blobs, types.BlobInfo{Digest: layer.Digest, Size: layer.Size, Annotations: layer.Annotations})
|
blobs = append(blobs, types.BlobInfo{Digest: layer.Digest, Size: layer.Size, Annotations: layer.Annotations, URLs: layer.URLs})
|
||||||
}
|
}
|
||||||
return blobs
|
return blobs
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,7 @@ func (m *manifestOCI1) UpdatedImage(options types.ManifestUpdateOptions) (types.
|
||||||
copy.LayersDescriptors[i].Digest = info.Digest
|
copy.LayersDescriptors[i].Digest = info.Digest
|
||||||
copy.LayersDescriptors[i].Size = info.Size
|
copy.LayersDescriptors[i].Size = info.Size
|
||||||
copy.LayersDescriptors[i].Annotations = info.Annotations
|
copy.LayersDescriptors[i].Annotations = info.Annotations
|
||||||
|
copy.LayersDescriptors[i].URLs = info.URLs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ignore options.EmbeddedDockerReference: it may be set when converting from schema1, but we really don't care.
|
// Ignore options.EmbeddedDockerReference: it may be set when converting from schema1, but we really don't care.
|
||||||
|
|
6
vendor/github.com/containers/image/oci/archive/oci_dest.go
generated
vendored
6
vendor/github.com/containers/image/oci/archive/oci_dest.go
generated
vendored
|
@ -106,11 +106,7 @@ func (d *ociArchiveImageDestination) Commit() error {
|
||||||
src := d.tempDirRef.tempDirectory
|
src := d.tempDirRef.tempDirectory
|
||||||
// path to save tarred up file
|
// path to save tarred up file
|
||||||
dst := d.ref.resolvedFile
|
dst := d.ref.resolvedFile
|
||||||
if err := tarDirectory(src, dst); err != nil {
|
return tarDirectory(src, dst)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tar converts the directory at src and saves it to dst
|
// tar converts the directory at src and saves it to dst
|
||||||
|
|
2
vendor/github.com/containers/image/oci/layout/oci_dest.go
generated
vendored
2
vendor/github.com/containers/image/oci/layout/oci_dest.go
generated
vendored
|
@ -66,7 +66,7 @@ func (d *ociImageDestination) ShouldCompressLayers() bool {
|
||||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||||
// uploaded to the image destination, true otherwise.
|
// uploaded to the image destination, true otherwise.
|
||||||
func (d *ociImageDestination) AcceptsForeignLayerURLs() bool {
|
func (d *ociImageDestination) AcceptsForeignLayerURLs() bool {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
|
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
|
||||||
|
|
55
vendor/github.com/containers/image/oci/layout/oci_src.go
generated
vendored
55
vendor/github.com/containers/image/oci/layout/oci_src.go
generated
vendored
|
@ -4,25 +4,43 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/containers/image/pkg/tlsclientconfig"
|
||||||
"github.com/containers/image/types"
|
"github.com/containers/image/types"
|
||||||
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ociImageSource struct {
|
type ociImageSource struct {
|
||||||
ref ociReference
|
ref ociReference
|
||||||
descriptor imgspecv1.Descriptor
|
descriptor imgspecv1.Descriptor
|
||||||
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||||
func newImageSource(ref ociReference) (types.ImageSource, error) {
|
func newImageSource(ctx *types.SystemContext, ref ociReference) (types.ImageSource, error) {
|
||||||
|
tr := tlsclientconfig.NewTransport()
|
||||||
|
tr.TLSClientConfig = tlsconfig.ServerDefault()
|
||||||
|
|
||||||
|
if ctx != nil && ctx.OCICertPath != "" {
|
||||||
|
if err := tlsclientconfig.SetupCertificates(ctx.OCICertPath, tr.TLSClientConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tr.TLSClientConfig.InsecureSkipVerify = ctx.OCIInsecureSkipTLSVerify
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
client.Transport = tr
|
||||||
descriptor, err := ref.getManifestDescriptor()
|
descriptor, err := ref.getManifestDescriptor()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ociImageSource{ref: ref, descriptor: descriptor}, nil
|
return &ociImageSource{ref: ref, descriptor: descriptor, client: client}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference returns the reference used to set up this source.
|
// Reference returns the reference used to set up this source.
|
||||||
|
@ -70,6 +88,10 @@ func (s *ociImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string
|
||||||
|
|
||||||
// GetBlob returns a stream for the specified blob, and the blob's size.
|
// GetBlob returns a stream for the specified blob, and the blob's size.
|
||||||
func (s *ociImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
func (s *ociImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||||
|
if len(info.URLs) != 0 {
|
||||||
|
return s.getExternalBlob(info.URLs)
|
||||||
|
}
|
||||||
|
|
||||||
path, err := s.ref.blobPath(info.Digest)
|
path, err := s.ref.blobPath(info.Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
@ -89,3 +111,32 @@ func (s *ociImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, err
|
||||||
func (s *ociImageSource) GetSignatures(context.Context) ([][]byte, error) {
|
func (s *ociImageSource) GetSignatures(context.Context) ([][]byte, error) {
|
||||||
return [][]byte{}, nil
|
return [][]byte{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ociImageSource) getExternalBlob(urls []string) (io.ReadCloser, int64, error) {
|
||||||
|
errWrap := errors.New("failed fetching external blob from all urls")
|
||||||
|
for _, url := range urls {
|
||||||
|
resp, err := s.client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", url, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
resp.Body.Close()
|
||||||
|
errWrap = errors.Wrapf(errWrap, "fetching %s failed, response code not 200", url)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Body, getBlobSize(resp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, errWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBlobSize(resp *http.Response) int64 {
|
||||||
|
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
size = -1
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
4
vendor/github.com/containers/image/oci/layout/oci_transport.go
generated
vendored
4
vendor/github.com/containers/image/oci/layout/oci_transport.go
generated
vendored
|
@ -182,7 +182,7 @@ func (ref ociReference) PolicyConfigurationNamespaces() []string {
|
||||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||||
func (ref ociReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
|
func (ref ociReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
|
||||||
src, err := newImageSource(ref)
|
src, err := newImageSource(ctx, ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -244,7 +244,7 @@ func LoadManifestDescriptor(imgRef types.ImageReference) (imgspecv1.Descriptor,
|
||||||
// NewImageSource returns a types.ImageSource for this reference.
|
// NewImageSource returns a types.ImageSource for this reference.
|
||||||
// The caller must call .Close() on the returned ImageSource.
|
// The caller must call .Close() on the returned ImageSource.
|
||||||
func (ref ociReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
func (ref ociReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||||
return newImageSource(ref)
|
return newImageSource(ctx, ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||||
|
|
19
vendor/github.com/containers/image/ostree/ostree_dest.go
generated
vendored
19
vendor/github.com/containers/image/ostree/ostree_dest.go
generated
vendored
|
@ -46,6 +46,7 @@ type ostreeImageDestination struct {
|
||||||
schema manifestSchema
|
schema manifestSchema
|
||||||
tmpDirPath string
|
tmpDirPath string
|
||||||
blobs map[string]*blobToImport
|
blobs map[string]*blobToImport
|
||||||
|
digest digest.Digest
|
||||||
}
|
}
|
||||||
|
|
||||||
// newImageDestination returns an ImageDestination for writing to an existing ostree.
|
// newImageDestination returns an ImageDestination for writing to an existing ostree.
|
||||||
|
@ -54,7 +55,7 @@ func newImageDestination(ref ostreeReference, tmpDirPath string) (types.ImageDes
|
||||||
if err := ensureDirectoryExists(tmpDirPath); err != nil {
|
if err := ensureDirectoryExists(tmpDirPath); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ostreeImageDestination{ref, "", manifestSchema{}, tmpDirPath, map[string]*blobToImport{}}, nil
|
return &ostreeImageDestination{ref, "", manifestSchema{}, tmpDirPath, map[string]*blobToImport{}, ""}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||||||
|
@ -238,10 +239,10 @@ func (d *ostreeImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInf
|
||||||
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
||||||
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
||||||
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
||||||
func (d *ostreeImageDestination) PutManifest(manifest []byte) error {
|
func (d *ostreeImageDestination) PutManifest(manifestBlob []byte) error {
|
||||||
d.manifest = string(manifest)
|
d.manifest = string(manifestBlob)
|
||||||
|
|
||||||
if err := json.Unmarshal(manifest, &d.schema); err != nil {
|
if err := json.Unmarshal(manifestBlob, &d.schema); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +251,13 @@ func (d *ostreeImageDestination) PutManifest(manifest []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(manifestPath, manifest, 0644)
|
digest, err := manifest.Digest(manifestBlob)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.digest = digest
|
||||||
|
|
||||||
|
return ioutil.WriteFile(manifestPath, manifestBlob, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ostreeImageDestination) PutSignatures(signatures [][]byte) error {
|
func (d *ostreeImageDestination) PutSignatures(signatures [][]byte) error {
|
||||||
|
@ -304,7 +311,7 @@ func (d *ostreeImageDestination) Commit() error {
|
||||||
|
|
||||||
manifestPath := filepath.Join(d.tmpDirPath, "manifest")
|
manifestPath := filepath.Join(d.tmpDirPath, "manifest")
|
||||||
|
|
||||||
metadata := []string{fmt.Sprintf("docker.manifest=%s", string(d.manifest))}
|
metadata := []string{fmt.Sprintf("docker.manifest=%s", string(d.manifest)), fmt.Sprintf("docker.digest=%s", string(d.digest))}
|
||||||
err = d.ostreeCommit(repo, fmt.Sprintf("ociimage/%s", d.ref.branchName), manifestPath, metadata)
|
err = d.ostreeCommit(repo, fmt.Sprintf("ociimage/%s", d.ref.branchName), manifestPath, metadata)
|
||||||
|
|
||||||
_, err = repo.CommitTransaction()
|
_, err = repo.CommitTransaction()
|
||||||
|
|
295
vendor/github.com/containers/image/pkg/docker/config/config.go
generated
vendored
Normal file
295
vendor/github.com/containers/image/pkg/docker/config/config.go
generated
vendored
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containers/image/types"
|
||||||
|
helperclient "github.com/docker/docker-credential-helpers/client"
|
||||||
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dockerAuthConfig struct {
|
||||||
|
Auth string `json:"auth,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dockerConfigFile struct {
|
||||||
|
AuthConfigs map[string]dockerAuthConfig `json:"auths"`
|
||||||
|
CredHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPath = "/run/user"
|
||||||
|
authCfg = "containers"
|
||||||
|
authCfgFileName = "auth.json"
|
||||||
|
dockerCfg = ".docker"
|
||||||
|
dockerCfgFileName = "config.json"
|
||||||
|
dockerLegacyCfg = ".dockercfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNotLoggedIn is returned for users not logged into a registry
|
||||||
|
// that they are trying to logout of
|
||||||
|
ErrNotLoggedIn = errors.New("not logged in")
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetAuthentication stores the username and password in the auth.json file
|
||||||
|
func SetAuthentication(ctx *types.SystemContext, registry, username, password string) error {
|
||||||
|
return modifyJSON(ctx, func(auths *dockerConfigFile) (bool, error) {
|
||||||
|
if ch, exists := auths.CredHelpers[registry]; exists {
|
||||||
|
return false, setAuthToCredHelper(ch, registry, username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
creds := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
|
||||||
|
newCreds := dockerAuthConfig{Auth: creds}
|
||||||
|
auths.AuthConfigs[registry] = newCreds
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthentication returns the registry credentials stored in
|
||||||
|
// either auth.json file or .docker/config.json
|
||||||
|
// If an entry is not found empty strings are returned for the username and password
|
||||||
|
func GetAuthentication(ctx *types.SystemContext, registry string) (string, string, error) {
|
||||||
|
if ctx != nil && ctx.DockerAuthConfig != nil {
|
||||||
|
return ctx.DockerAuthConfig.Username, ctx.DockerAuthConfig.Password, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerLegacyPath := filepath.Join(homedir.Get(), dockerLegacyCfg)
|
||||||
|
paths := [3]string{getPathToAuth(ctx), filepath.Join(homedir.Get(), dockerCfg, dockerCfgFileName), dockerLegacyPath}
|
||||||
|
|
||||||
|
for _, path := range paths {
|
||||||
|
legacyFormat := path == dockerLegacyPath
|
||||||
|
username, password, err := findAuthentication(registry, path, legacyFormat)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if username != "" && password != "" {
|
||||||
|
return username, password, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserLoggedIn returns the username logged in to registry from either
|
||||||
|
// auth.json or XDG_RUNTIME_DIR
|
||||||
|
// Used to tell the user if someone is logged in to the registry when logging in
|
||||||
|
func GetUserLoggedIn(ctx *types.SystemContext, registry string) string {
|
||||||
|
path := getPathToAuth(ctx)
|
||||||
|
username, _, _ := findAuthentication(registry, path, false)
|
||||||
|
if username != "" {
|
||||||
|
return username
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAuthentication deletes the credentials stored in auth.json
|
||||||
|
func RemoveAuthentication(ctx *types.SystemContext, registry string) error {
|
||||||
|
return modifyJSON(ctx, func(auths *dockerConfigFile) (bool, error) {
|
||||||
|
// First try cred helpers.
|
||||||
|
if ch, exists := auths.CredHelpers[registry]; exists {
|
||||||
|
return false, deleteAuthFromCredHelper(ch, registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := auths.AuthConfigs[registry]; ok {
|
||||||
|
delete(auths.AuthConfigs, registry)
|
||||||
|
} else if _, ok := auths.AuthConfigs[normalizeRegistry(registry)]; ok {
|
||||||
|
delete(auths.AuthConfigs, normalizeRegistry(registry))
|
||||||
|
} else {
|
||||||
|
return false, ErrNotLoggedIn
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllAuthentication deletes all the credentials stored in auth.json
|
||||||
|
func RemoveAllAuthentication(ctx *types.SystemContext) error {
|
||||||
|
return modifyJSON(ctx, func(auths *dockerConfigFile) (bool, error) {
|
||||||
|
auths.CredHelpers = make(map[string]string)
|
||||||
|
auths.AuthConfigs = make(map[string]dockerAuthConfig)
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPath gets the path of the auth.json file
|
||||||
|
// The path can be overriden by the user if the overwrite-path flag is set
|
||||||
|
// If the flag is not set and XDG_RUNTIME_DIR is ser, the auth.json file is saved in XDG_RUNTIME_DIR/containers
|
||||||
|
// Otherwise, the auth.json file is stored in /run/user/UID/containers
|
||||||
|
func getPathToAuth(ctx *types.SystemContext) string {
|
||||||
|
if ctx != nil {
|
||||||
|
if ctx.AuthFilePath != "" {
|
||||||
|
return ctx.AuthFilePath
|
||||||
|
}
|
||||||
|
if ctx.RootForImplicitAbsolutePaths != "" {
|
||||||
|
return filepath.Join(ctx.RootForImplicitAbsolutePaths, defaultPath, strconv.Itoa(os.Getuid()), authCfg, authCfgFileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
||||||
|
if runtimeDir == "" {
|
||||||
|
runtimeDir = filepath.Join(defaultPath, strconv.Itoa(os.Getuid()))
|
||||||
|
}
|
||||||
|
return filepath.Join(runtimeDir, authCfg, authCfgFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readJSONFile unmarshals the authentications stored in the auth.json file and returns it
|
||||||
|
// or returns an empty dockerConfigFile data structure if auth.json does not exist
|
||||||
|
// if the file exists and is empty, readJSONFile returns an error
|
||||||
|
func readJSONFile(path string, legacyFormat bool) (dockerConfigFile, error) {
|
||||||
|
var auths dockerConfigFile
|
||||||
|
|
||||||
|
raw, err := ioutil.ReadFile(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
auths.AuthConfigs = map[string]dockerAuthConfig{}
|
||||||
|
return auths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if legacyFormat {
|
||||||
|
if err = json.Unmarshal(raw, &auths.AuthConfigs); err != nil {
|
||||||
|
return dockerConfigFile{}, errors.Wrapf(err, "error unmarshaling JSON at %q", path)
|
||||||
|
}
|
||||||
|
return auths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(raw, &auths); err != nil {
|
||||||
|
return dockerConfigFile{}, errors.Wrapf(err, "error unmarshaling JSON at %q", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return auths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// modifyJSON writes to auth.json if the dockerConfigFile has been updated
|
||||||
|
func modifyJSON(ctx *types.SystemContext, editor func(auths *dockerConfigFile) (bool, error)) error {
|
||||||
|
path := getPathToAuth(ctx)
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
|
if err = os.Mkdir(dir, 0700); err != nil {
|
||||||
|
return errors.Wrapf(err, "error creating directory %q", dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auths, err := readJSONFile(path, false)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error reading JSON file %q", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := editor(&auths)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error updating %q", path)
|
||||||
|
}
|
||||||
|
if updated {
|
||||||
|
newData, err := json.MarshalIndent(auths, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error marshaling JSON %q", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(path, newData, 0755); err != nil {
|
||||||
|
return errors.Wrapf(err, "error writing to file %q", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuthFromCredHelper(credHelper, registry string) (string, string, error) {
|
||||||
|
helperName := fmt.Sprintf("docker-credential-%s", credHelper)
|
||||||
|
p := helperclient.NewShellProgramFunc(helperName)
|
||||||
|
creds, err := helperclient.Get(p, registry)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return creds.Username, creds.Secret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAuthToCredHelper(credHelper, registry, username, password string) error {
|
||||||
|
helperName := fmt.Sprintf("docker-credential-%s", credHelper)
|
||||||
|
p := helperclient.NewShellProgramFunc(helperName)
|
||||||
|
creds := &credentials.Credentials{
|
||||||
|
ServerURL: registry,
|
||||||
|
Username: username,
|
||||||
|
Secret: password,
|
||||||
|
}
|
||||||
|
return helperclient.Store(p, creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteAuthFromCredHelper(credHelper, registry string) error {
|
||||||
|
helperName := fmt.Sprintf("docker-credential-%s", credHelper)
|
||||||
|
p := helperclient.NewShellProgramFunc(helperName)
|
||||||
|
return helperclient.Erase(p, registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findAuthentication looks for auth of registry in path
|
||||||
|
func findAuthentication(registry, path string, legacyFormat bool) (string, string, error) {
|
||||||
|
auths, err := readJSONFile(path, legacyFormat)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", errors.Wrapf(err, "error reading JSON file %q", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First try cred helpers. They should always be normalized.
|
||||||
|
if ch, exists := auths.CredHelpers[registry]; exists {
|
||||||
|
return getAuthFromCredHelper(ch, registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// I'm feeling lucky
|
||||||
|
if val, exists := auths.AuthConfigs[registry]; exists {
|
||||||
|
return decodeDockerAuth(val.Auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad luck; let's normalize the entries first
|
||||||
|
registry = normalizeRegistry(registry)
|
||||||
|
normalizedAuths := map[string]dockerAuthConfig{}
|
||||||
|
for k, v := range auths.AuthConfigs {
|
||||||
|
normalizedAuths[normalizeRegistry(k)] = v
|
||||||
|
}
|
||||||
|
if val, exists := normalizedAuths[registry]; exists {
|
||||||
|
return decodeDockerAuth(val.Auth)
|
||||||
|
}
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeDockerAuth(s string) (string, string, error) {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(string(decoded), ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
// if it's invalid just skip, as docker does
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
user := parts[0]
|
||||||
|
password := strings.Trim(parts[1], "\x00")
|
||||||
|
return user, password, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToHostname converts a registry url which has http|https prepended
|
||||||
|
// to just an hostname.
|
||||||
|
// Copied from github.com/docker/docker/registry/auth.go
|
||||||
|
func convertToHostname(url string) string {
|
||||||
|
stripped := url
|
||||||
|
if strings.HasPrefix(url, "http://") {
|
||||||
|
stripped = strings.TrimPrefix(url, "http://")
|
||||||
|
} else if strings.HasPrefix(url, "https://") {
|
||||||
|
stripped = strings.TrimPrefix(url, "https://")
|
||||||
|
}
|
||||||
|
|
||||||
|
nameParts := strings.SplitN(stripped, "/", 2)
|
||||||
|
|
||||||
|
return nameParts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeRegistry(registry string) string {
|
||||||
|
normalized := convertToHostname(registry)
|
||||||
|
switch normalized {
|
||||||
|
case "registry-1.docker.io", "docker.io":
|
||||||
|
return "index.docker.io"
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
102
vendor/github.com/containers/image/pkg/tlsclientconfig/tlsclientconfig.go
generated
vendored
Normal file
102
vendor/github.com/containers/image/pkg/tlsclientconfig/tlsclientconfig.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package tlsclientconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/go-connections/sockets"
|
||||||
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetupCertificates opens all .crt, .cert, and .key files in dir and appends / loads certs and key pairs as appropriate to tlsc
|
||||||
|
func SetupCertificates(dir string, tlsc *tls.Config) error {
|
||||||
|
logrus.Debugf("Looking for TLS certificates and private keys in %s", dir)
|
||||||
|
fs, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
logrus.Debugf("Skipping scan of %s due to permission error: %v", dir, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range fs {
|
||||||
|
fullPath := filepath.Join(dir, f.Name())
|
||||||
|
if strings.HasSuffix(f.Name(), ".crt") {
|
||||||
|
systemPool, err := tlsconfig.SystemCertPool()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to get system cert pool")
|
||||||
|
}
|
||||||
|
tlsc.RootCAs = systemPool
|
||||||
|
logrus.Debugf(" crt: %s", fullPath)
|
||||||
|
data, err := ioutil.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tlsc.RootCAs.AppendCertsFromPEM(data)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(f.Name(), ".cert") {
|
||||||
|
certName := f.Name()
|
||||||
|
keyName := certName[:len(certName)-5] + ".key"
|
||||||
|
logrus.Debugf(" cert: %s", fullPath)
|
||||||
|
if !hasFile(fs, keyName) {
|
||||||
|
return errors.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName)
|
||||||
|
}
|
||||||
|
cert, err := tls.LoadX509KeyPair(filepath.Join(dir, certName), filepath.Join(dir, keyName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tlsc.Certificates = append(tlsc.Certificates, cert)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(f.Name(), ".key") {
|
||||||
|
keyName := f.Name()
|
||||||
|
certName := keyName[:len(keyName)-4] + ".cert"
|
||||||
|
logrus.Debugf(" key: %s", fullPath)
|
||||||
|
if !hasFile(fs, certName) {
|
||||||
|
return errors.Errorf("missing client certificate %s for key %s", certName, keyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasFile(files []os.FileInfo, name string) bool {
|
||||||
|
for _, f := range files {
|
||||||
|
if f.Name() == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTransport Creates a default transport
|
||||||
|
func NewTransport() *http.Transport {
|
||||||
|
direct := &net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
DualStack: true,
|
||||||
|
}
|
||||||
|
tr := &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
Dial: direct.Dial,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
// TODO(dmcgowan): Call close idle connections when complete and use keep alive
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
}
|
||||||
|
proxyDialer, err := sockets.DialerFromEnvironment(direct)
|
||||||
|
if err == nil {
|
||||||
|
tr.Dial = proxyDialer.Dial
|
||||||
|
}
|
||||||
|
return tr
|
||||||
|
}
|
8
vendor/github.com/containers/image/signature/signature.go
generated
vendored
8
vendor/github.com/containers/image/signature/signature.go
generated
vendored
|
@ -180,13 +180,9 @@ func (s *untrustedSignature) strictUnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
s.UntrustedDockerManifestDigest = digest.Digest(digestString)
|
s.UntrustedDockerManifestDigest = digest.Digest(digestString)
|
||||||
|
|
||||||
if err := paranoidUnmarshalJSONObjectExactFields(identity, map[string]interface{}{
|
return paranoidUnmarshalJSONObjectExactFields(identity, map[string]interface{}{
|
||||||
"docker-reference": &s.UntrustedDockerReference,
|
"docker-reference": &s.UntrustedDockerReference,
|
||||||
}); err != nil {
|
})
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign formats the signature and returns a blob signed using mech and keyIdentity
|
// Sign formats the signature and returns a blob signed using mech and keyIdentity
|
||||||
|
|
10
vendor/github.com/containers/image/types/types.go
generated
vendored
10
vendor/github.com/containers/image/types/types.go
generated
vendored
|
@ -304,6 +304,16 @@ type SystemContext struct {
|
||||||
RegistriesDirPath string
|
RegistriesDirPath string
|
||||||
// Path to the system-wide registries configuration file
|
// Path to the system-wide registries configuration file
|
||||||
SystemRegistriesConfPath string
|
SystemRegistriesConfPath string
|
||||||
|
// If not "", overrides the default path for the authentication file
|
||||||
|
AuthFilePath string
|
||||||
|
|
||||||
|
// === OCI.Transport overrides ===
|
||||||
|
// If not "", a directory containing a CA certificate (ending with ".crt"),
|
||||||
|
// a client certificate (ending with ".cert") and a client ceritificate key
|
||||||
|
// (ending with ".key") used when downloading OCI image layers.
|
||||||
|
OCICertPath string
|
||||||
|
// Allow downloading OCI image layers over HTTP, or HTTPS with failed TLS verification. Note that this does not affect other TLS connections.
|
||||||
|
OCIInsecureSkipTLSVerify bool
|
||||||
|
|
||||||
// === docker.Transport overrides ===
|
// === docker.Transport overrides ===
|
||||||
// If not "", a directory containing a CA certificate (ending with ".crt"),
|
// If not "", a directory containing a CA certificate (ending with ".crt"),
|
||||||
|
|
23
vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go
generated
vendored
Normal file
23
vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package homedir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/idtools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetStatic returns the home directory for the current user without calling
|
||||||
|
// os/user.Current(). This is useful for static-linked binary on glibc-based
|
||||||
|
// system, because a call to os/user.Current() in a static binary leads to
|
||||||
|
// segfault due to a glibc issue that won't be fixed in a short term.
|
||||||
|
// (#29344, golang/go#13470, https://sourceware.org/bugzilla/show_bug.cgi?id=19341)
|
||||||
|
func GetStatic() (string, error) {
|
||||||
|
uid := os.Getuid()
|
||||||
|
usr, err := idtools.LookupUID(uid)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return usr.Home, nil
|
||||||
|
}
|
13
vendor/github.com/docker/docker/pkg/homedir/homedir_others.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/homedir/homedir_others.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package homedir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetStatic is not needed for non-linux systems.
|
||||||
|
// (Precisely, it is needed only for glibc-based linux systems.)
|
||||||
|
func GetStatic() (string, error) {
|
||||||
|
return "", errors.New("homedir.GetStatic() is not supported on this system")
|
||||||
|
}
|
34
vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go
generated
vendored
Normal file
34
vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package homedir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key returns the env var name for the user's home dir based on
|
||||||
|
// the platform being run on
|
||||||
|
func Key() string {
|
||||||
|
return "HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the home directory of the current user with the help of
|
||||||
|
// environment variables depending on the target operating system.
|
||||||
|
// Returned path should be used with "path/filepath" to form new paths.
|
||||||
|
func Get() string {
|
||||||
|
home := os.Getenv(Key())
|
||||||
|
if home == "" {
|
||||||
|
if u, err := user.CurrentUser(); err == nil {
|
||||||
|
return u.Home
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return home
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetShortcutString returns the string that is shortcut to user's home directory
|
||||||
|
// in the native shell of the platform running on.
|
||||||
|
func GetShortcutString() string {
|
||||||
|
return "~"
|
||||||
|
}
|
24
vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go
generated
vendored
Normal file
24
vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package homedir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key returns the env var name for the user's home dir based on
|
||||||
|
// the platform being run on
|
||||||
|
func Key() string {
|
||||||
|
return "USERPROFILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the home directory of the current user with the help of
|
||||||
|
// environment variables depending on the target operating system.
|
||||||
|
// Returned path should be used with "path/filepath" to form new paths.
|
||||||
|
func Get() string {
|
||||||
|
return os.Getenv(Key())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetShortcutString returns the string that is shortcut to user's home directory
|
||||||
|
// in the native shell of the platform running on.
|
||||||
|
func GetShortcutString() string {
|
||||||
|
return "%USERPROFILE%" // be careful while using in format functions
|
||||||
|
}
|
279
vendor/github.com/docker/docker/pkg/idtools/idtools.go
generated
vendored
Normal file
279
vendor/github.com/docker/docker/pkg/idtools/idtools.go
generated
vendored
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IDMap contains a single entry for user namespace range remapping. An array
|
||||||
|
// of IDMap entries represents the structure that will be provided to the Linux
|
||||||
|
// kernel for creating a user namespace.
|
||||||
|
type IDMap struct {
|
||||||
|
ContainerID int `json:"container_id"`
|
||||||
|
HostID int `json:"host_id"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type subIDRange struct {
|
||||||
|
Start int
|
||||||
|
Length int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ranges []subIDRange
|
||||||
|
|
||||||
|
func (e ranges) Len() int { return len(e) }
|
||||||
|
func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||||
|
func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
|
||||||
|
|
||||||
|
const (
|
||||||
|
subuidFileName string = "/etc/subuid"
|
||||||
|
subgidFileName string = "/etc/subgid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MkdirAllAs creates a directory (include any along the path) and then modifies
|
||||||
|
// ownership to the requested uid/gid. If the directory already exists, this
|
||||||
|
// function will still change ownership to the requested uid/gid pair.
|
||||||
|
// Deprecated: Use MkdirAllAndChown
|
||||||
|
func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
||||||
|
return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
|
||||||
|
// If the directory already exists, this function still changes ownership
|
||||||
|
// Deprecated: Use MkdirAndChown with a IDPair
|
||||||
|
func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
||||||
|
return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAllAndChown creates a directory (include any along the path) and then modifies
|
||||||
|
// ownership to the requested uid/gid. If the directory already exists, this
|
||||||
|
// function will still change ownership to the requested uid/gid pair.
|
||||||
|
func MkdirAllAndChown(path string, mode os.FileMode, ids IDPair) error {
|
||||||
|
return mkdirAs(path, mode, ids.UID, ids.GID, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
|
||||||
|
// If the directory already exists, this function still changes ownership
|
||||||
|
func MkdirAndChown(path string, mode os.FileMode, ids IDPair) error {
|
||||||
|
return mkdirAs(path, mode, ids.UID, ids.GID, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
|
||||||
|
// ownership ONLY of newly created directories to the requested uid/gid. If the
|
||||||
|
// directories along the path exist, no change of ownership will be performed
|
||||||
|
func MkdirAllAndChownNew(path string, mode os.FileMode, ids IDPair) error {
|
||||||
|
return mkdirAs(path, mode, ids.UID, ids.GID, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
|
||||||
|
// If the maps are empty, then the root uid/gid will default to "real" 0/0
|
||||||
|
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
|
||||||
|
uid, err := toHost(0, uidMap)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
gid, err := toHost(0, gidMap)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
return uid, gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toContainer takes an id mapping, and uses it to translate a
|
||||||
|
// host ID to the remapped ID. If no map is provided, then the translation
|
||||||
|
// assumes a 1-to-1 mapping and returns the passed in id
|
||||||
|
func toContainer(hostID int, idMap []IDMap) (int, error) {
|
||||||
|
if idMap == nil {
|
||||||
|
return hostID, nil
|
||||||
|
}
|
||||||
|
for _, m := range idMap {
|
||||||
|
if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
|
||||||
|
contID := m.ContainerID + (hostID - m.HostID)
|
||||||
|
return contID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// toHost takes an id mapping and a remapped ID, and translates the
|
||||||
|
// ID to the mapped host ID. If no map is provided, then the translation
|
||||||
|
// assumes a 1-to-1 mapping and returns the passed in id #
|
||||||
|
func toHost(contID int, idMap []IDMap) (int, error) {
|
||||||
|
if idMap == nil {
|
||||||
|
return contID, nil
|
||||||
|
}
|
||||||
|
for _, m := range idMap {
|
||||||
|
if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
|
||||||
|
hostID := m.HostID + (contID - m.ContainerID)
|
||||||
|
return hostID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDPair is a UID and GID pair
|
||||||
|
type IDPair struct {
|
||||||
|
UID int
|
||||||
|
GID int
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDMappings contains a mappings of UIDs and GIDs
|
||||||
|
type IDMappings struct {
|
||||||
|
uids []IDMap
|
||||||
|
gids []IDMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIDMappings takes a requested user and group name and
|
||||||
|
// using the data from /etc/sub{uid,gid} ranges, creates the
|
||||||
|
// proper uid and gid remapping ranges for that user/group pair
|
||||||
|
func NewIDMappings(username, groupname string) (*IDMappings, error) {
|
||||||
|
subuidRanges, err := parseSubuid(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
subgidRanges, err := parseSubgid(groupname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(subuidRanges) == 0 {
|
||||||
|
return nil, fmt.Errorf("No subuid ranges found for user %q", username)
|
||||||
|
}
|
||||||
|
if len(subgidRanges) == 0 {
|
||||||
|
return nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IDMappings{
|
||||||
|
uids: createIDMap(subuidRanges),
|
||||||
|
gids: createIDMap(subgidRanges),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIDMappingsFromMaps creates a new mapping from two slices
|
||||||
|
// Deprecated: this is a temporary shim while transitioning to IDMapping
|
||||||
|
func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings {
|
||||||
|
return &IDMappings{uids: uids, gids: gids}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootPair returns a uid and gid pair for the root user. The error is ignored
|
||||||
|
// because a root user always exists, and the defaults are correct when the uid
|
||||||
|
// and gid maps are empty.
|
||||||
|
func (i *IDMappings) RootPair() IDPair {
|
||||||
|
uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
|
||||||
|
return IDPair{UID: uid, GID: gid}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHost returns the host UID and GID for the container uid, gid.
|
||||||
|
// Remapping is only performed if the ids aren't already the remapped root ids
|
||||||
|
func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) {
|
||||||
|
var err error
|
||||||
|
target := i.RootPair()
|
||||||
|
|
||||||
|
if pair.UID != target.UID {
|
||||||
|
target.UID, err = toHost(pair.UID, i.uids)
|
||||||
|
if err != nil {
|
||||||
|
return target, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pair.GID != target.GID {
|
||||||
|
target.GID, err = toHost(pair.GID, i.gids)
|
||||||
|
}
|
||||||
|
return target, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToContainer returns the container UID and GID for the host uid and gid
|
||||||
|
func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) {
|
||||||
|
uid, err := toContainer(pair.UID, i.uids)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
gid, err := toContainer(pair.GID, i.gids)
|
||||||
|
return uid, gid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty returns true if there are no id mappings
|
||||||
|
func (i *IDMappings) Empty() bool {
|
||||||
|
return len(i.uids) == 0 && len(i.gids) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UIDs return the UID mapping
|
||||||
|
// TODO: remove this once everything has been refactored to use pairs
|
||||||
|
func (i *IDMappings) UIDs() []IDMap {
|
||||||
|
return i.uids
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIDs return the UID mapping
|
||||||
|
// TODO: remove this once everything has been refactored to use pairs
|
||||||
|
func (i *IDMappings) GIDs() []IDMap {
|
||||||
|
return i.gids
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIDMap(subidRanges ranges) []IDMap {
|
||||||
|
idMap := []IDMap{}
|
||||||
|
|
||||||
|
// sort the ranges by lowest ID first
|
||||||
|
sort.Sort(subidRanges)
|
||||||
|
containerID := 0
|
||||||
|
for _, idrange := range subidRanges {
|
||||||
|
idMap = append(idMap, IDMap{
|
||||||
|
ContainerID: containerID,
|
||||||
|
HostID: idrange.Start,
|
||||||
|
Size: idrange.Length,
|
||||||
|
})
|
||||||
|
containerID = containerID + idrange.Length
|
||||||
|
}
|
||||||
|
return idMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSubuid(username string) (ranges, error) {
|
||||||
|
return parseSubidFile(subuidFileName, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSubgid(username string) (ranges, error) {
|
||||||
|
return parseSubidFile(subgidFileName, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
|
||||||
|
// and return all found ranges for a specified username. If the special value
|
||||||
|
// "ALL" is supplied for username, then all ranges in the file will be returned
|
||||||
|
func parseSubidFile(path, username string) (ranges, error) {
|
||||||
|
var rangeList ranges
|
||||||
|
|
||||||
|
subidFile, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return rangeList, err
|
||||||
|
}
|
||||||
|
defer subidFile.Close()
|
||||||
|
|
||||||
|
s := bufio.NewScanner(subidFile)
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return rangeList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := strings.TrimSpace(s.Text())
|
||||||
|
if text == "" || strings.HasPrefix(text, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Split(text, ":")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
|
||||||
|
}
|
||||||
|
if parts[0] == username || username == "ALL" {
|
||||||
|
startid, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
|
||||||
|
}
|
||||||
|
length, err := strconv.Atoi(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
|
||||||
|
}
|
||||||
|
rangeList = append(rangeList, subIDRange{startid, length})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rangeList, nil
|
||||||
|
}
|
204
vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go
generated
vendored
Normal file
204
vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/system"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
entOnce sync.Once
|
||||||
|
getentCmd string
|
||||||
|
)
|
||||||
|
|
||||||
|
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
||||||
|
// make an array containing the original path asked for, plus (for mkAll == true)
|
||||||
|
// all path components leading up to the complete path that don't exist before we MkdirAll
|
||||||
|
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
|
||||||
|
// chown the full directory path if it exists
|
||||||
|
var paths []string
|
||||||
|
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
||||||
|
paths = []string{path}
|
||||||
|
} else if err == nil && chownExisting {
|
||||||
|
// short-circuit--we were called with an existing directory and chown was requested
|
||||||
|
return os.Chown(path, ownerUID, ownerGID)
|
||||||
|
} else if err == nil {
|
||||||
|
// nothing to do; directory path fully exists already and chown was NOT requested
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if mkAll {
|
||||||
|
// walk back to "/" looking for directories which do not exist
|
||||||
|
// and add them to the paths array for chown after creation
|
||||||
|
dirPath := path
|
||||||
|
for {
|
||||||
|
dirPath = filepath.Dir(dirPath)
|
||||||
|
if dirPath == "/" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
|
||||||
|
paths = append(paths, dirPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := system.MkdirAll(path, mode, ""); err != nil && !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// even if it existed, we will chown the requested path + any subpaths that
|
||||||
|
// didn't exist when we called MkdirAll
|
||||||
|
for _, pathComponent := range paths {
|
||||||
|
if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||||
|
// if that uid, gid pair has access (execute bit) to the directory
|
||||||
|
func CanAccess(path string, pair IDPair) bool {
|
||||||
|
statInfo, err := system.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fileMode := os.FileMode(statInfo.Mode())
|
||||||
|
permBits := fileMode.Perm()
|
||||||
|
return accessible(statInfo.UID() == uint32(pair.UID),
|
||||||
|
statInfo.GID() == uint32(pair.GID), permBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
|
||||||
|
if isOwner && (perms&0100 == 0100) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isGroup && (perms&0010 == 0010) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if perms&0001 == 0001 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
|
||||||
|
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||||
|
func LookupUser(username string) (user.User, error) {
|
||||||
|
// first try a local system files lookup using existing capabilities
|
||||||
|
usr, err := user.LookupUser(username)
|
||||||
|
if err == nil {
|
||||||
|
return usr, nil
|
||||||
|
}
|
||||||
|
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||||
|
usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username))
|
||||||
|
if err != nil {
|
||||||
|
return user.User{}, err
|
||||||
|
}
|
||||||
|
return usr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
|
||||||
|
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||||
|
func LookupUID(uid int) (user.User, error) {
|
||||||
|
// first try a local system files lookup using existing capabilities
|
||||||
|
usr, err := user.LookupUid(uid)
|
||||||
|
if err == nil {
|
||||||
|
return usr, nil
|
||||||
|
}
|
||||||
|
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||||
|
return getentUser(fmt.Sprintf("%s %d", "passwd", uid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getentUser(args string) (user.User, error) {
|
||||||
|
reader, err := callGetent(args)
|
||||||
|
if err != nil {
|
||||||
|
return user.User{}, err
|
||||||
|
}
|
||||||
|
users, err := user.ParsePasswd(reader)
|
||||||
|
if err != nil {
|
||||||
|
return user.User{}, err
|
||||||
|
}
|
||||||
|
if len(users) == 0 {
|
||||||
|
return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1])
|
||||||
|
}
|
||||||
|
return users[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
|
||||||
|
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||||
|
func LookupGroup(groupname string) (user.Group, error) {
|
||||||
|
// first try a local system files lookup using existing capabilities
|
||||||
|
group, err := user.LookupGroup(groupname)
|
||||||
|
if err == nil {
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||||
|
return getentGroup(fmt.Sprintf("%s %s", "group", groupname))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
|
||||||
|
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||||
|
func LookupGID(gid int) (user.Group, error) {
|
||||||
|
// first try a local system files lookup using existing capabilities
|
||||||
|
group, err := user.LookupGid(gid)
|
||||||
|
if err == nil {
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||||
|
return getentGroup(fmt.Sprintf("%s %d", "group", gid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getentGroup(args string) (user.Group, error) {
|
||||||
|
reader, err := callGetent(args)
|
||||||
|
if err != nil {
|
||||||
|
return user.Group{}, err
|
||||||
|
}
|
||||||
|
groups, err := user.ParseGroup(reader)
|
||||||
|
if err != nil {
|
||||||
|
return user.Group{}, err
|
||||||
|
}
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1])
|
||||||
|
}
|
||||||
|
return groups[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func callGetent(args string) (io.Reader, error) {
|
||||||
|
entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
|
||||||
|
// if no `getent` command on host, can't do anything else
|
||||||
|
if getentCmd == "" {
|
||||||
|
return nil, fmt.Errorf("")
|
||||||
|
}
|
||||||
|
out, err := execCmd(getentCmd, args)
|
||||||
|
if err != nil {
|
||||||
|
exitCode, errC := system.GetExitCode(err)
|
||||||
|
if errC != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch exitCode {
|
||||||
|
case 1:
|
||||||
|
return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
|
||||||
|
case 2:
|
||||||
|
terms := strings.Split(args, " ")
|
||||||
|
return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0])
|
||||||
|
case 3:
|
||||||
|
return nil, fmt.Errorf("getent database doesn't support enumeration")
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return bytes.NewReader(out), nil
|
||||||
|
}
|
25
vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
25
vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Platforms such as Windows do not support the UID/GID concept. So make this
|
||||||
|
// just a wrapper around system.MkdirAll.
|
||||||
|
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
||||||
|
if err := system.MkdirAll(path, mode, ""); err != nil && !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||||
|
// if that uid, gid pair has access (execute bit) to the directory
|
||||||
|
// Windows does not require/support this function, so always return true
|
||||||
|
func CanAccess(path string, pair IDPair) bool {
|
||||||
|
return true
|
||||||
|
}
|
164
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
164
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
|
||||||
|
// Linux distribution commands:
|
||||||
|
// adduser --system --shell /bin/false --disabled-login --disabled-password --no-create-home --group <username>
|
||||||
|
// useradd -r -s /bin/false <username>
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
userCommand string
|
||||||
|
|
||||||
|
cmdTemplates = map[string]string{
|
||||||
|
"adduser": "--system --shell /bin/false --no-create-home --disabled-login --disabled-password --group %s",
|
||||||
|
"useradd": "-r -s /bin/false %s",
|
||||||
|
"usermod": "-%s %d-%d %s",
|
||||||
|
}
|
||||||
|
|
||||||
|
idOutRegexp = regexp.MustCompile(`uid=([0-9]+).*gid=([0-9]+)`)
|
||||||
|
// default length for a UID/GID subordinate range
|
||||||
|
defaultRangeLen = 65536
|
||||||
|
defaultRangeStart = 100000
|
||||||
|
userMod = "usermod"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddNamespaceRangesUser takes a username and uses the standard system
|
||||||
|
// utility to create a system user/group pair used to hold the
|
||||||
|
// /etc/sub{uid,gid} ranges which will be used for user namespace
|
||||||
|
// mapping ranges in containers.
|
||||||
|
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||||
|
if err := addUser(name); err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the system for the created uid and gid pair
|
||||||
|
out, err := execCmd("id", name)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("Error trying to find uid/gid for new user %q: %v", name, err)
|
||||||
|
}
|
||||||
|
matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out)))
|
||||||
|
if len(matches) != 3 {
|
||||||
|
return -1, -1, fmt.Errorf("Can't find uid, gid from `id` output: %q", string(out))
|
||||||
|
}
|
||||||
|
uid, err := strconv.Atoi(matches[1])
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("Can't convert found uid (%s) to int: %v", matches[1], err)
|
||||||
|
}
|
||||||
|
gid, err := strconv.Atoi(matches[2])
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("Can't convert found gid (%s) to int: %v", matches[2], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we need to create the subuid/subgid ranges for our new user/group (system users
|
||||||
|
// do not get auto-created ranges in subuid/subgid)
|
||||||
|
|
||||||
|
if err := createSubordinateRanges(name); err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("Couldn't create subordinate ID ranges: %v", err)
|
||||||
|
}
|
||||||
|
return uid, gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUser(userName string) error {
|
||||||
|
once.Do(func() {
|
||||||
|
// set up which commands are used for adding users/groups dependent on distro
|
||||||
|
if _, err := resolveBinary("adduser"); err == nil {
|
||||||
|
userCommand = "adduser"
|
||||||
|
} else if _, err := resolveBinary("useradd"); err == nil {
|
||||||
|
userCommand = "useradd"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if userCommand == "" {
|
||||||
|
return fmt.Errorf("Cannot add user; no useradd/adduser binary found")
|
||||||
|
}
|
||||||
|
args := fmt.Sprintf(cmdTemplates[userCommand], userName)
|
||||||
|
out, err := execCmd(userCommand, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to add user with error: %v; output: %q", err, string(out))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSubordinateRanges(name string) error {
|
||||||
|
|
||||||
|
// first, we should verify that ranges weren't automatically created
|
||||||
|
// by the distro tooling
|
||||||
|
ranges, err := parseSubuid(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error while looking for subuid ranges for user %q: %v", name, err)
|
||||||
|
}
|
||||||
|
if len(ranges) == 0 {
|
||||||
|
// no UID ranges; let's create one
|
||||||
|
startID, err := findNextUIDRange()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Can't find available subuid range: %v", err)
|
||||||
|
}
|
||||||
|
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "v", startID, startID+defaultRangeLen-1, name))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to add subuid range to user: %q; output: %s, err: %v", name, out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges, err = parseSubgid(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error while looking for subgid ranges for user %q: %v", name, err)
|
||||||
|
}
|
||||||
|
if len(ranges) == 0 {
|
||||||
|
// no GID ranges; let's create one
|
||||||
|
startID, err := findNextGIDRange()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Can't find available subgid range: %v", err)
|
||||||
|
}
|
||||||
|
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "w", startID, startID+defaultRangeLen-1, name))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to add subgid range to user: %q; output: %s, err: %v", name, out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNextUIDRange() (int, error) {
|
||||||
|
ranges, err := parseSubuid("ALL")
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subuid file: %v", err)
|
||||||
|
}
|
||||||
|
sort.Sort(ranges)
|
||||||
|
return findNextRangeStart(ranges)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNextGIDRange() (int, error) {
|
||||||
|
ranges, err := parseSubgid("ALL")
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subgid file: %v", err)
|
||||||
|
}
|
||||||
|
sort.Sort(ranges)
|
||||||
|
return findNextRangeStart(ranges)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNextRangeStart(rangeList ranges) (int, error) {
|
||||||
|
startID := defaultRangeStart
|
||||||
|
for _, arange := range rangeList {
|
||||||
|
if wouldOverlap(arange, startID) {
|
||||||
|
startID = arange.Start + arange.Length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return startID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wouldOverlap(arange subIDRange, ID int) bool {
|
||||||
|
low := ID
|
||||||
|
high := ID + defaultRangeLen
|
||||||
|
if (low >= arange.Start && low <= arange.Start+arange.Length) ||
|
||||||
|
(high <= arange.Start+arange.Length && high >= arange.Start) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
12
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
|
||||||
|
// and calls the appropriate helper function to add the group and then
|
||||||
|
// the user to the group in /etc/group and /etc/passwd respectively.
|
||||||
|
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||||
|
return -1, -1, fmt.Errorf("No support for adding users or groups on this OS")
|
||||||
|
}
|
32
vendor/github.com/docker/docker/pkg/idtools/utils_unix.go
generated
vendored
Normal file
32
vendor/github.com/docker/docker/pkg/idtools/utils_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolveBinary(binname string) (string, error) {
|
||||||
|
binaryPath, err := exec.LookPath(binname)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
resolvedPath, err := filepath.EvalSymlinks(binaryPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
//only return no error if the final resolved binary basename
|
||||||
|
//matches what was searched for
|
||||||
|
if filepath.Base(resolvedPath) == binname {
|
||||||
|
return resolvedPath, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execCmd(cmd, args string) ([]byte, error) {
|
||||||
|
execCmd := exec.Command(cmd, strings.Split(args, " ")...)
|
||||||
|
return execCmd.CombinedOutput()
|
||||||
|
}
|
Loading…
Reference in a new issue