Skip to content

Commit

Permalink
Merge pull request #2190 from hashicorp/b-docker-auth
Browse files Browse the repository at this point in the history
Better Docker Auth lookup
  • Loading branch information
dadgar authored Jan 23, 2017
2 parents 7181c5a + 77b19f5 commit 40d776d
Show file tree
Hide file tree
Showing 33 changed files with 3,757 additions and 31 deletions.
79 changes: 53 additions & 26 deletions client/driver/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import (

docker "github.com/fsouza/go-dockerclient"

"github.com/docker/docker/cli/config/configfile"
"github.com/docker/docker/reference"
"github.com/docker/docker/registry"

"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/nomad/client/allocdir"
Expand Down Expand Up @@ -135,7 +139,6 @@ type DockerDriverConfig struct {
LabelsRaw []map[string]string `mapstructure:"labels"` //
Labels map[string]string `mapstructure:"-"` // Labels to set when the container starts up
Auth []DockerDriverAuth `mapstructure:"auth"` // Authentication credentials for a private Docker registry
SSL bool `mapstructure:"ssl"` // Flag indicating repository is served via https
TTY bool `mapstructure:"tty"` // Allocate a Pseudo-TTY
Interactive bool `mapstructure:"interactive"` // Keep STDIN open even if not attached
ShmSize int64 `mapstructure:"shm_size"` // Size of /dev/shm of the container in bytes
Expand Down Expand Up @@ -164,9 +167,6 @@ func (c *DockerDriverConfig) Validate() error {
func NewDockerDriverConfig(task *structs.Task, env *env.TaskEnvironment) (*DockerDriverConfig, error) {
var dconf DockerDriverConfig

// Default to SSL
dconf.SSL = true

if err := mapstructure.WeakDecode(task.Config, &dconf); err != nil {
return nil, err
}
Expand Down Expand Up @@ -315,6 +315,7 @@ func (d *DockerDriver) Validate(config map[string]interface{}) error {
"auth": &fields.FieldSchema{
Type: fields.TypeArray,
},
// COMPAT: Remove in 0.6.0. SSL is no longer needed
"ssl": &fields.FieldSchema{
Type: fields.TypeBool,
},
Expand Down Expand Up @@ -985,29 +986,17 @@ func (d *DockerDriver) pullImage(driverConfig *DockerDriverConfig, client *docke
Email: driverConfig.Auth[0].Email,
ServerAddress: driverConfig.Auth[0].ServerAddress,
}
}

if authConfigFile := d.config.Read("docker.auth.config"); authConfigFile != "" {
if f, err := os.Open(authConfigFile); err == nil {
defer f.Close()
var authConfigurations *docker.AuthConfigurations
if authConfigurations, err = docker.NewAuthConfigurations(f); err != nil {
return fmt.Errorf("Failed to create docker auth object: %v", err)
}

authConfigurationKey := ""
if driverConfig.SSL {
authConfigurationKey += "https://"
}
} else if authConfigFile := d.config.Read("docker.auth.config"); authConfigFile != "" {
authOptionsPtr, err := authOptionFrom(authConfigFile, repo)
if err != nil {
d.logger.Printf("[INFO] driver.docker: failed to find docker auth for repo %q: %v", repo, err)
return fmt.Errorf("Failed to find docker auth for repo %q: %v", repo, err)
}

authConfigurationKey += strings.Split(driverConfig.ImageName, "/")[0]
if authConfiguration, ok := authConfigurations.Configs[authConfigurationKey]; ok {
authOptions = authConfiguration
} else {
d.logger.Printf("[INFO] Failed to find docker auth with key %s", authConfigurationKey)
}
} else {
return fmt.Errorf("Failed to open auth config file: %v, error: %v", authConfigFile, err)
authOptions = *authOptionsPtr
if authOptions.Email == "" && authOptions.Password == "" &&
authOptions.ServerAddress == "" && authOptions.Username == "" {
d.logger.Printf("[DEBUG] driver.docker: did not find docker auth for repo %q", repo)
}
}

Expand Down Expand Up @@ -1404,3 +1393,41 @@ func calculatePercent(newSample, oldSample, newTotal, oldTotal uint64, cores int

return (float64(numerator) / float64(denom)) * float64(cores) * 100.0
}

// authOptionFrom takes the Docker auth config file and the repo being pulled
// and returns an AuthConfiguration or an error if the file/repo could not be
// parsed or looked up.
func authOptionFrom(file, repo string) (*docker.AuthConfiguration, error) {
name, err := reference.ParseNamed(repo)
if err != nil {
return nil, fmt.Errorf("Failed to parse named repo %q: %v", err)
}

repoInfo, err := registry.ParseRepositoryInfo(name)
if err != nil {
return nil, fmt.Errorf("Failed to parse repository: %v", err)
}

f, err := os.Open(file)
if err != nil {
return nil, fmt.Errorf("Failed to open auth config file: %v, error: %v", file, err)
}
defer f.Close()

cfile := new(configfile.ConfigFile)
if err := cfile.LoadFromReader(f); err != nil {
return nil, fmt.Errorf("Failed to parse auth config file: %v", err)
}

dockerAuthConfig := registry.ResolveAuthConfig(cfile.AuthConfigs, repoInfo.Index)

// Convert to Api version
apiAuthConfig := &docker.AuthConfiguration{
Username: dockerAuthConfig.Username,
Password: dockerAuthConfig.Password,
Email: dockerAuthConfig.Email,
ServerAddress: dockerAuthConfig.ServerAddress,
}

return apiAuthConfig, nil
}
51 changes: 51 additions & 0 deletions client/driver/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1330,3 +1330,54 @@ func copyImage(t *testing.T, taskDir *allocdir.TaskDir, image string) {
dst := filepath.Join(taskDir.LocalDir, image)
copyFile(filepath.Join("./test-resources/docker", image), dst, t)
}

func TestDockerDriver_AuthConfiguration(t *testing.T) {
path := "./test-resources/docker/auth.json"
cases := []struct {
Repo string
AuthConfig *docker.AuthConfiguration
}{
{
Repo: "lolwhat.com/what:1337",
AuthConfig: &docker.AuthConfiguration{},
},
{
Repo: "redis:3.2",
AuthConfig: &docker.AuthConfiguration{
Username: "test",
Password: "1234",
Email: "",
ServerAddress: "https://index.docker.io/v1/",
},
},
{
Repo: "quay.io/redis:3.2",
AuthConfig: &docker.AuthConfiguration{
Username: "test",
Password: "5678",
Email: "",
ServerAddress: "quay.io",
},
},
{
Repo: "other.io/redis:3.2",
AuthConfig: &docker.AuthConfiguration{
Username: "test",
Password: "abcd",
Email: "",
ServerAddress: "https://other.io/v1/",
},
},
}

for i, c := range cases {
act, err := authOptionFrom(path, c.Repo)
if err != nil {
t.Fatalf("Test %d failed: %v", i+1, err)
}

if !reflect.DeepEqual(act, c.AuthConfig) {
t.Fatalf("Test %d failed: Unexpected auth config: got %+v; want %+v", i+1, act, c.AuthConfig)
}
}
}
13 changes: 13 additions & 0 deletions client/driver/test-resources/docker/auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "dGVzdDoxMjM0"
},
"quay.io": {
"auth": "dGVzdDo1Njc4"
},
"https://other.io/v1/": {
"auth": "dGVzdDphYmNk"
}
}
}
202 changes: 202 additions & 0 deletions vendor/github.com/docker/distribution/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 40d776d

Please sign in to comment.