Skip to content

Commit

Permalink
Merge pull request #733 from dragan/enhancement/http-proxy
Browse files Browse the repository at this point in the history
Allow configuring a proxy that ignition can honor during replace and append fetches
  • Loading branch information
ajeddeloh authored Apr 11, 2019
2 parents 32c77e3 + aba88f4 commit b757ecd
Show file tree
Hide file tree
Showing 51 changed files with 31,814 additions and 10 deletions.
20 changes: 20 additions & 0 deletions config/v3_1_experimental/schema/ignition.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
},
"security": {
"$ref": "#/definitions/ignition/definitions/security"
},
"proxy": {
"$ref": "#/definitions/ignition/definitions/proxy"
}
},
"definitions": {
Expand Down Expand Up @@ -73,6 +76,23 @@
}
}
},
"proxy": {
"type": "object",
"properties": {
"httpProxy": {
"type": ["string", "null"]
},
"httpsProxy": {
"type": ["string", "null"]
},
"noProxy": {
"type": "array",
"items": {
"type": ["string", "null"]
}
}
}
},
"config-reference": {
"type": "object",
"properties": {
Expand Down
5 changes: 4 additions & 1 deletion config/v3_1_experimental/translate/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import (

func translateIgnition(old old_types.Ignition) (ret types.Ignition) {
// use a new translator so we don't recurse infintitely
translate.NewTranslator().Translate(&old, &ret)
tr := translate.NewTranslator()
tr.Translate(&old.Config, &ret.Config)
tr.Translate(&old.Security, &ret.Security)
tr.Translate(&old.Timeouts, &ret.Timeouts)
ret.Version = types.MaxVersion.String()
return
}
Expand Down
9 changes: 9 additions & 0 deletions config/v3_1_experimental/types/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type Group string

type Ignition struct {
Config IgnitionConfig `json:"config,omitempty"`
Proxy Proxy `json:"proxy,omitempty"`
Security Security `json:"security,omitempty"`
Timeouts Timeouts `json:"timeouts,omitempty"`
Version string `json:"version,omitempty"`
Expand All @@ -94,6 +95,8 @@ type LinkEmbedded1 struct {
Target string `json:"target"`
}

type NoProxyItem string

type Node struct {
Group NodeGroup `json:"group,omitempty"`
Overwrite *bool `json:"overwrite,omitempty"`
Expand Down Expand Up @@ -150,6 +153,12 @@ type PasswdUser struct {
UID *int `json:"uid,omitempty"`
}

type Proxy struct {
HTTPProxy *string `json:"httpProxy,omitempty"`
HTTPSProxy *string `json:"httpsProxy,omitempty"`
NoProxy []NoProxyItem `json:"noProxy,omitempty"`
}

type Raid struct {
Devices []Device `json:"devices"`
Level string `json:"level"`
Expand Down
4 changes: 4 additions & 0 deletions doc/configuration-v3_1.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ The Ignition configuration is a JSON document conforming to the following specif
* **source** (string): the URL of the certificate (in PEM format). Supported schemes are `http`, `https`, `s3`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified.
* **_verification_** (object): options related to the verification of the certificate.
* **_hash_** (string): the hash of the certificate, in the form `<type>-<value>` where type is sha512.
* **_proxy_** (object): options relating to setting an `HTTP(S)` proxy when fetching resources.
* **_httpProxy_** (string): will be used as the proxy URL for HTTP requests and HTTPS requests unless overridden by `httpsProxy` or `noProxy`.
* **_httpsProxy_** (string): will be used as the proxy URL for HTTPS requests unless overridden by `noProxy`.
* **_noProxy_** (list of strings): specifies a list of strings to hosts that should be excluded from proxying. Each value is represented by an `IP address prefix (1.2.3.4)`, `an IP address prefix in CIDR notation (1.2.3.4/8)`, `a domain name`, or `a special DNS label (*)`. An IP address prefix and domain name can also include a literal port number `(1.2.3.4:80)`. A domain name matches that name and all subdomains. A domain name with a leading `.` matches subdomains only. For example `foo.com` matches `foo.com` and `bar.foo.com`; `.y.com` matches `x.y.com` but not `y.com`. A single asterisk `(*)` indicates that no proxying should be done.
* **_storage_** (object): describes the desired state of the system's storage devices.
* **_disks_** (list of objects): the list of disks to be configured and their options.
* **device** (string): the absolute path to the device. Devices are typically referenced by the `/dev/disk/by-*` symlinks.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/vincent-petithory/dataurl v0.0.0-20160330182126-9a301d65acbb
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728 // indirect
github.com/vmware/vmw-ovflib v0.0.0-20170608004843-1f217b9dc714
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7 // indirect
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7
golang.org/x/text v0.3.0 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)
13 changes: 7 additions & 6 deletions internal/exec/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (e *Engine) acquireConfig() (cfg types.Config, err error) {
}
// Create an http client and fetcher with the timeouts from the cached
// config
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(cfg.Ignition.Timeouts, cfg.Ignition.Security.TLS.CertificateAuthorities)
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(cfg.Ignition.Timeouts, cfg.Ignition.Security.TLS.CertificateAuthorities, cfg.Ignition.Proxy)
if err != nil {
e.Logger.Crit("failed to update timeouts and CAs for fetcher: %v", err)
return
Expand All @@ -124,7 +124,8 @@ func (e *Engine) acquireConfig() (cfg types.Config, err error) {
// Create a new http client and fetcher with the timeouts set via the flags,
// since we don't have a config with timeout values we can use
timeout := int(e.FetchTimeout.Seconds())
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(types.Timeouts{HTTPTotal: &timeout}, nil)
emptyProxy := types.Proxy{}
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(types.Timeouts{HTTPTotal: &timeout}, nil, emptyProxy)
if err != nil {
e.Logger.Crit("failed to update timeouts and CAs for fetcher: %v", err)
return
Expand All @@ -139,7 +140,7 @@ func (e *Engine) acquireConfig() (cfg types.Config, err error) {

// Update the http client to use the timeouts and CAs from the newly fetched
// config
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(cfg.Ignition.Timeouts, cfg.Ignition.Security.TLS.CertificateAuthorities)
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(cfg.Ignition.Timeouts, cfg.Ignition.Security.TLS.CertificateAuthorities, cfg.Ignition.Proxy)
if err != nil {
e.Logger.Crit("failed to update timeouts and CAs for fetcher: %v", err)
return
Expand Down Expand Up @@ -205,7 +206,7 @@ func (e *Engine) fetchProviderConfig() (types.Config, error) {

// Replace the HTTP client in the fetcher to be configured with the
// timeouts of the config
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(cfg.Ignition.Timeouts, cfg.Ignition.Security.TLS.CertificateAuthorities)
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(cfg.Ignition.Timeouts, cfg.Ignition.Security.TLS.CertificateAuthorities, cfg.Ignition.Proxy)
if err != nil {
return types.Config{}, err
}
Expand All @@ -229,7 +230,7 @@ func (e *Engine) renderConfig(cfg types.Config) (types.Config, error) {

// Replace the HTTP client in the fetcher to be configured with the
// timeouts of the new config
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(newCfg.Ignition.Timeouts, newCfg.Ignition.Security.TLS.CertificateAuthorities)
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(newCfg.Ignition.Timeouts, newCfg.Ignition.Security.TLS.CertificateAuthorities, newCfg.Ignition.Proxy)
if err != nil {
return types.Config{}, err
}
Expand All @@ -248,7 +249,7 @@ func (e *Engine) renderConfig(cfg types.Config) (types.Config, error) {
// been rendered, so we can use the new config's timeouts and CAs when
// fetching more configs.
cfgForFetcherSettings := latest.Merge(appendedCfg, newCfg)
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(cfgForFetcherSettings.Ignition.Timeouts, cfgForFetcherSettings.Ignition.Security.TLS.CertificateAuthorities)
err = e.Fetcher.UpdateHttpTimeoutsAndCAs(cfgForFetcherSettings.Ignition.Timeouts, cfgForFetcherSettings.Ignition.Security.TLS.CertificateAuthorities, cfgForFetcherSettings.Ignition.Proxy)
if err != nil {
return types.Config{}, err
}
Expand Down
41 changes: 39 additions & 2 deletions internal/resource/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"net"
"net/http"
"net/url"
"strings"
"time"

"github.com/coreos/ignition/v2/config/v3_1_experimental/types"
Expand All @@ -34,6 +35,8 @@ import (
"github.com/coreos/ignition/v2/internal/version"

"github.com/vincent-petithory/dataurl"

"golang.org/x/net/http/httpproxy"
)

const (
Expand All @@ -60,7 +63,7 @@ type HttpClient struct {
cas map[types.CaReference][]byte
}

func (f *Fetcher) UpdateHttpTimeoutsAndCAs(timeouts types.Timeouts, cas []types.CaReference) error {
func (f *Fetcher) UpdateHttpTimeoutsAndCAs(timeouts types.Timeouts, cas []types.CaReference, proxy types.Proxy) error {
if f.client == nil {
if err := f.newHttpClient(); err != nil {
return err
Expand All @@ -83,6 +86,12 @@ func (f *Fetcher) UpdateHttpTimeoutsAndCAs(timeouts types.Timeouts, cas []types.
f.client.transport.ResponseHeaderTimeout = time.Duration(responseHeader) * time.Second
f.client.client.Transport = f.client.transport

// Update proxy
f.client.transport.Proxy = func(req *http.Request) (*url.URL, error) {
return proxyFuncFromIgnitionConfig(proxy)(req.URL)
}
f.client.client.Transport = f.client.transport

// Update CAs
if len(cas) == 0 {
return nil
Expand Down Expand Up @@ -116,7 +125,7 @@ func (f *Fetcher) UpdateHttpTimeoutsAndCAs(timeouts types.Timeouts, cas []types.
}

f.client.transport.TLSClientConfig = &tls.Config{RootCAs: pool}
f.client.client.Transport = f.client.transport

return nil
}

Expand Down Expand Up @@ -272,3 +281,31 @@ func (c HttpClient) getReaderWithHeader(url string, header http.Header) (io.Read
}
}
}

func proxyFuncFromIgnitionConfig(proxy types.Proxy) func(*url.URL) (*url.URL, error) {
noProxy := translateNoProxySliceToString(proxy.NoProxy)

if proxy.HTTPProxy == nil {
proxy.HTTPProxy = new(string)
}

if proxy.HTTPSProxy == nil {
proxy.HTTPSProxy = new(string)
}

cfg := &httpproxy.Config{
HTTPProxy: *proxy.HTTPProxy,
HTTPSProxy: *proxy.HTTPSProxy,
NoProxy: noProxy,
}

return cfg.ProxyFunc()
}

func translateNoProxySliceToString(items []types.NoProxyItem) string {
newItems := make([]string, len(items))
for i, o := range items {
newItems[i] = string(o)
}
return strings.Join(newItems, ",")
}
71 changes: 71 additions & 0 deletions tests/negative/proxy/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2019 Red Hat, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package proxy

import (
"fmt"
"net/http"
"net/http/httptest"

"github.com/coreos/ignition/v2/tests/register"
"github.com/coreos/ignition/v2/tests/types"
)

var (
proxyServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Proxy is unavailable", http.StatusServiceUnavailable)
return
}))

mockConfigURL = "http://www.fake.tld"
)

func init() {
register.Register(register.NegativeTest, ErrorsWhenProxyIsUnavailable())
}

func ErrorsWhenProxyIsUnavailable() types.Test {
name := "Errors When Proxy Is Unavailable"
in := types.GetBaseDisk()
out := types.GetBaseDisk()
config := fmt.Sprintf(`{
"ignition": {
"version": "$version",
"config": {
"replace": {
"source": "%s/"
}
},
"proxy": {
"httpProxy": "%s",
"httpsProxy": "%s",
"noProxy": [""]
},
"timeouts": {
"httpResponseHeaders": 1,
"httpTotal": 1
}
}
}`, mockConfigURL, proxyServer.URL, proxyServer.URL)
configMinVersion := "3.1.0-experimental"

return types.Test{
Name: name,
In: in,
Out: out,
Config: config,
ConfigMinVersion: configMinVersion,
}
}
Loading

0 comments on commit b757ecd

Please sign in to comment.