Skip to content
This repository has been archived by the owner on Feb 24, 2020. It is now read-only.

Support HTTP Basic authentication when fetching dependencies #254

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Documentation/subcommands/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,38 @@ In order to be able to run the command, all dependencies of the current ACI
must be fetched. The first time `run` is called, the dependencies will be
downloaded and expanded.

### Authentication

acbuild can use HTTP Basic authentication when fetching dependencies. To use
authentication, specify a directory using the `--auth-config-dir` flag. By
default acbuild will look in `auth.d`.

acbuild looks for configuration files with a `.json` file name extension in the
specified directory and its subdirectories. Each file is expected to contain
two fields: `domains` and `credentials`.

The `domains` field is an array of strings describing hosts for which the
following credentials should be used. Each entry must consist of a host in a
URL as specified by RFC 3986.

The `credentials` field is a map with two keys - `user` and `password`. These
should be the values needed for successful authentication with the given
hosts.

For example:

`auth.d/coreos-basic.json`

```
{
"domains": ["coreos.com", "tectonic.com"],
"credentials": {
"user": "foo",
"password": "bar"
}
}
```

## Overlayfs

acbuild utilizes overlayfs when running a command in an ACI with dependencies.
Expand Down
2 changes: 1 addition & 1 deletion acbuild/acbuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func runWrapper(cf func(cmd *cobra.Command, args []string) (exit int)) func(cmd

a := newACBuild()

err = a.Begin(absoluteAciToModify, false)
err = a.Begin(absoluteAciToModify, false, "")
if err != nil {
stderr("%v", err)
cmdExitCode = getErrorCode(err)
Expand Down
5 changes: 3 additions & 2 deletions acbuild/begin.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
func init() {
cmdAcbuild.AddCommand(cmdBegin)
cmdBegin.Flags().BoolVar(&insecure, "insecure", false, "Allows fetching dependencies over an unencrypted connection")
cmdBegin.Flags().StringVar(&authConfigDir, "auth-config-dir", "auth.d", "Directory with authentication config file(s)")
}

func runBegin(cmd *cobra.Command, args []string) (exit int) {
Expand All @@ -49,9 +50,9 @@ func runBegin(cmd *cobra.Command, args []string) (exit int) {

var err error
if len(args) == 0 {
err = newACBuild().Begin("", insecure)
err = newACBuild().Begin("", insecure, authConfigDir)
} else {
err = newACBuild().Begin(args[0], insecure)
err = newACBuild().Begin(args[0], insecure, authConfigDir)
}

if err != nil {
Expand Down
13 changes: 8 additions & 5 deletions acbuild/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ import (
)

var (
insecure = false
workingdir = ""
engineName = ""
cmdRun = &cobra.Command{
insecure = false
workingdir = ""
engineName = ""
authConfigDir = ""

cmdRun = &cobra.Command{
Use: "run -- CMD [ARGS]",
Short: "Run a command in an ACI",
Long: "Run a given command in an ACI, and save the resulting container as a new ACI",
Expand All @@ -55,6 +57,7 @@ func init() {
cmdRun.Flags().BoolVar(&insecure, "insecure", false, "Allows fetching dependencies over http")
cmdRun.Flags().StringVar(&workingdir, "working-dir", "", "The working directory inside the container for this command")
cmdRun.Flags().StringVar(&engineName, "engine", "systemd-nspawn", "The engine used to run the command. Supported engines: "+engineList)
cmdRun.Flags().StringVar(&authConfigDir, "auth-config-dir", "auth.d", "Directory with authentication config file(s)")
}

func runRun(cmd *cobra.Command, args []string) (exit int) {
Expand All @@ -73,7 +76,7 @@ func runRun(cmd *cobra.Command, args []string) (exit int) {
return 1
}

err := newACBuild().Run(args, workingdir, insecure, engine)
err := newACBuild().Run(args, workingdir, insecure, engine, authConfigDir)

if err != nil {
stderr("run: %v", err)
Expand Down
13 changes: 10 additions & 3 deletions lib/begin.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ var (
// at a.CurrentACIPath. If start is the empty string, the build will begin with
// an empty ACI, otherwise the ACI stored at start will be used at the starting
// point.
func (a *ACBuild) Begin(start string, insecure bool) (err error) {
func (a *ACBuild) Begin(start string, insecure bool, authConfigDir string) (err error) {
_, err = os.Stat(a.ContextPath)
switch {
case os.IsNotExist(err):
Expand Down Expand Up @@ -103,7 +103,7 @@ func (a *ACBuild) Begin(start string, insecure bool) (err error) {
start = strings.TrimPrefix(start, dockerPrefix)
return a.beginFromRemoteDockerImage(start, insecure)
}
return a.beginFromRemoteImage(start, insecure)
return a.beginFromRemoteImage(start, insecure, authConfigDir)
}
}
return a.beginWithEmptyACI()
Expand Down Expand Up @@ -252,7 +252,7 @@ func (a *ACBuild) writeEmptyManifest() error {
return nil
}

func (a *ACBuild) beginFromRemoteImage(start string, insecure bool) error {
func (a *ACBuild) beginFromRemoteImage(start string, insecure bool, authConfigDir string) error {
app, err := discovery.NewAppFromString(start)
if err != nil {
return err
Expand All @@ -274,9 +274,15 @@ func (a *ACBuild) beginFromRemoteImage(start string, insecure bool) error {
}
defer os.RemoveAll(tmpDepStoreExpandedPath)

authConfig, err := registry.ReadAuthConfig(authConfigDir)
if err != nil {
return err
}

reg := registry.Registry{
DepStoreTarPath: tmpDepStoreTarPath,
DepStoreExpandedPath: tmpDepStoreExpandedPath,
AuthConfig: authConfig,
Insecure: insecure,
Debug: a.Debug,
}
Expand Down Expand Up @@ -333,6 +339,7 @@ func (a *ACBuild) beginFromRemoteDockerImage(start string, insecure bool) (err e
AllowHTTP: insecure,
}

// TODO: Docker authentication
config := docker2aci.RemoteConfig{
CommonConfig: docker2aci.CommonConfig{
Squash: true,
Expand Down
12 changes: 9 additions & 3 deletions lib/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import (
// changed to its value before running the given command.
//
// - runEngine: The engine used to perform the execution of the command.
func (a *ACBuild) Run(cmd []string, workingDir string, insecure bool, runEngine engine.Engine) (err error) {
func (a *ACBuild) Run(cmd []string, workingDir string, insecure bool, runEngine engine.Engine, authConfigDir string) (err error) {
if err = a.lock(); err != nil {
return err
}
Expand Down Expand Up @@ -110,7 +110,7 @@ func (a *ACBuild) Run(cmd []string, workingDir string, insecure bool, runEngine
}
}

deps, err := a.renderACI(insecure, a.Debug)
deps, err := a.renderACI(insecure, authConfigDir, a.Debug)
if err != nil {
return err
}
Expand Down Expand Up @@ -179,10 +179,16 @@ func supportsOverlay() bool {
return false
}

func (a *ACBuild) renderACI(insecure, debug bool) ([]string, error) {
func (a *ACBuild) renderACI(insecure bool, authConfigDir string, debug bool) ([]string, error) {
authConfig, err := registry.ReadAuthConfig(authConfigDir)
if err != nil {
return nil, err
}

reg := registry.Registry{
DepStoreTarPath: a.DepStoreTarPath,
DepStoreExpandedPath: a.DepStoreExpandedPath,
AuthConfig: authConfig,
Insecure: insecure,
Debug: debug,
}
Expand Down
81 changes: 81 additions & 0 deletions registry/auth-config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package registry

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
)

type HostHeaders map[string]http.Header

type AuthConfig []AuthFile

type AuthFile struct {
Domains []string `json:"domains"`
Credentials Credentials `json:"credentials"`
}

type Credentials struct {
User string `json:"user"`
Password string `json:"password"`
}

func ReadAuthConfig(directory string) (*AuthConfig, error) {
authConfig := AuthConfig{}

err := filepath.Walk(directory, func(path string, file os.FileInfo, err error) error {
if err != nil {
return nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to log here what went wrong?

}

if !validAuthFile(file) {
return nil
}

jsonContents, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("auth-config: %s", err)
}

authFile := AuthFile{}
err = json.Unmarshal(jsonContents, &authFile)
if err != nil {
return fmt.Errorf("auth-config: %s", err)
}

authConfig = append(authConfig, authFile)

return nil
})

return &authConfig, err
}

func validAuthFile(file os.FileInfo) bool {
if !file.Mode().IsRegular() {
return false
}

if filepath.Ext(file.Name()) != ".json" {
return false
}

return true
}

func (ac *AuthConfig) HostHeaders() HostHeaders {
hostHeaders := HostHeaders{}
for _, authFile := range *ac {
fakeRequest := http.Request{Header: http.Header{}}
fakeRequest.SetBasicAuth(authFile.Credentials.User, authFile.Credentials.Password)

for _, domain := range authFile.Domains {
hostHeaders[domain] = fakeRequest.Header
}
}

return hostHeaders
}
13 changes: 11 additions & 2 deletions registry/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ func (r Registry) discoverEndpoint(imageName types.ACIdentifier, labels types.La
insecure = discovery.InsecureHTTP
}

acis, attempts, err := discovery.DiscoverACIEndpoints(*app, nil, insecure, 0)
acis, attempts, err := discovery.DiscoverACIEndpoints(*app, r.AuthConfig.HostHeaders(), insecure, 0)
if err != nil {
return nil, err
}
Expand All @@ -355,11 +355,15 @@ func (r Registry) discoverEndpoint(imageName types.ACIdentifier, labels types.La
}

func (r Registry) download(url, path, label string) error {
//TODO: auth
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}

if header, ok := r.AuthConfig.HostHeaders()[req.URL.Host]; ok {
req.Header = header
}

transport := http.DefaultTransport
transport.(*http.Transport).Proxy = http.ProxyFromEnvironment
if r.Insecure {
Expand All @@ -373,6 +377,11 @@ func (r Registry) download(url, path, label string) error {
if len(via) >= 10 {
return fmt.Errorf("too many redirects")
}

if header, ok := r.AuthConfig.HostHeaders()[req.URL.Host]; ok {
req.Header = header
}

//f.setHTTPHeaders(req, etag)
return nil
}
Expand Down
1 change: 1 addition & 0 deletions registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ var (
type Registry struct {
DepStoreTarPath string
DepStoreExpandedPath string
AuthConfig *AuthConfig
Insecure bool
Debug bool
}
Expand Down