Skip to content

Commit 268338f

Browse files
Merge pull request #968 from sohankunkerkar/addCabundle
Add support for CA bundles for fetching the ignition config
2 parents 21207e1 + 0d3d0a3 commit 268338f

File tree

8 files changed

+355
-112
lines changed

8 files changed

+355
-112
lines changed

doc/configuration-v3_0.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The Ignition configuration is a JSON document conforming to the following specif
1919
* **_security_** (object): options relating to network security.
2020
* **_tls_** (object): options relating to TLS when fetching resources over `https`.
2121
* **_certificateAuthorities_** (list of objects): the list of additional certificate authorities (in addition to the system authorities) to be used for TLS verification when fetching over `https`. All certificate authorities must have a unique `source`.
22-
* **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.
22+
* **source** (string): the URL of the certificate bundle (in PEM format). The bundle can contain multiple concatenated certificates. 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.
2323
* **_verification_** (object): options related to the verification of the certificate.
2424
* **_hash_** (string): the hash of the certificate, in the form `<type>-<value>` where type is sha512.
2525
* **_storage_** (object): describes the desired state of the system's storage devices.

doc/configuration-v3_1.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ The Ignition configuration is a JSON document conforming to the following specif
2727
* **_security_** (object): options relating to network security.
2828
* **_tls_** (object): options relating to TLS when fetching resources over `https`.
2929
* **_certificateAuthorities_** (list of objects): the list of additional certificate authorities (in addition to the system authorities) to be used for TLS verification when fetching over `https`. All certificate authorities must have a unique `source`.
30-
* **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.
30+
* **source** (string): the URL of the certificate bundle (in PEM format). The bundle can contain multiple concatenated certificates. 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.
3131
* **_compression_** (string): the type of compression used on the certificate (null or gzip). Compression cannot be used with S3.
3232
* **_httpHeaders_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only.
3333
* **name** (string): the header name.

doc/configuration-v3_2_experimental.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ The Ignition configuration is a JSON document conforming to the following specif
2929
* **_security_** (object): options relating to network security.
3030
* **_tls_** (object): options relating to TLS when fetching resources over `https`.
3131
* **_certificateAuthorities_** (list of objects): the list of additional certificate authorities (in addition to the system authorities) to be used for TLS verification when fetching over `https`. All certificate authorities must have a unique `source`.
32-
* **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.
32+
* **source** (string): the URL of the certificate bundle (in PEM format). The bundle can contain multiple concatenated certificates. 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.
3333
* **_compression_** (string): the type of compression used on the certificate (null or gzip). Compression cannot be used with S3.
3434
* **_httpHeaders_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only.
3535
* **name** (string): the header name.

internal/resource/http.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -109,24 +109,32 @@ func (f *Fetcher) UpdateHttpTimeoutsAndCAs(timeouts types.Timeouts, cas []types.
109109
if err != nil {
110110
return err
111111
}
112-
block, _ := pem.Decode(cablob)
112+
if err := f.parseCABundle(cablob, ca, pool); err != nil {
113+
f.Logger.Err("Unable to parse CA bundle: %s", err)
114+
return err
115+
}
116+
}
117+
f.client.transport.TLSClientConfig = &tls.Config{RootCAs: pool}
118+
return nil
119+
}
120+
121+
// parseCABundle parses a CA bundle which includes multiple CAs.
122+
func (f *Fetcher) parseCABundle(cablob []byte, ca types.Resource, pool *x509.CertPool) error {
123+
for len(cablob) > 0 {
124+
block, rest := pem.Decode(cablob)
113125
if block == nil {
114126
f.Logger.Err("Unable to decode CA (%v)", ca.Source)
115127
return ErrPEMDecodeFailed
116128
}
117-
118129
cert, err := x509.ParseCertificate(block.Bytes)
119130
if err != nil {
120131
f.Logger.Err("Unable to parse CA (%v): %s", ca.Source, err)
121132
return err
122133
}
123-
124134
f.Logger.Info("Adding %q to list of CAs", cert.Subject.CommonName)
125135
pool.AddCert(cert)
136+
cablob = rest
126137
}
127-
128-
f.client.transport.TLSClientConfig = &tls.Config{RootCAs: pool}
129-
130138
return nil
131139
}
132140

tests/fixtures/tls_fixtures.go

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2020 Red Hat, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package fixtures
16+
17+
// This file contains all the large constants used for testing
18+
// the tls flow.
19+
20+
var (
21+
// PrivateKey is generated via:
22+
// openssl ecparam -genkey -name secp384r1 -out server.key
23+
PrivateKey = []byte(`-----BEGIN EC PARAMETERS-----
24+
BgUrgQQAIg==
25+
-----END EC PARAMETERS-----
26+
-----BEGIN EC PRIVATE KEY-----
27+
MIGkAgEBBDB6yW6RIYfTXdYVuPY0V0L6EtZ6vZD86vgbsw52Y3/U5nZ2JE++JrKu
28+
tt2Xt/NMzG6gBwYFK4EEACKhZANiAAQDEhfHEulYKlANw9eR5l455gwzAIQuraa0
29+
49RhvM7PPywaiD8DobteQmE8wn7cJSzOYw6GLvrL4Q1BO5EFUXknkW50t8lfnUeH
30+
veCNsqvm82F1NVevVoExAUhDYmMREa4=
31+
-----END EC PRIVATE KEY-----`)
32+
33+
// PublicKey is generated via:
34+
// generate csr:
35+
// openssl req -new -key server.key -out server.csr
36+
// generate certificate:
37+
// openssl x509 -req -days 3650 -in server.csr -signkey server.key -out
38+
// server.crt -extensions v3_req -extfile extfile.conf
39+
// where extfile.conf has the following details:
40+
// $ cat extfile.conf
41+
// [ v3_req ]
42+
// subjectAltName = IP:127.0.0.1
43+
// subjectKeyIdentifier=hash
44+
// authorityKeyIdentifier=keyid
45+
// basicConstraints = critical,CA:TRUE
46+
PublicKey = []byte(`-----BEGIN CERTIFICATE-----
47+
MIICzTCCAlKgAwIBAgIJALTP0pfNBMzGMAoGCCqGSM49BAMCMIGZMQswCQYDVQQG
48+
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj
49+
bzETMBEGA1UECgwKQ29yZU9TIEluYzEUMBIGA1UECwwLRW5naW5lZXJpbmcxEzAR
50+
BgNVBAMMCmNvcmVvcy5jb20xHTAbBgkqhkiG9w0BCQEWDm9lbUBjb3Jlb3MuY29t
51+
MB4XDTE4MDEyNTAwMDczOVoXDTI4MDEyMzAwMDczOVowgZkxCzAJBgNVBAYTAlVT
52+
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMw
53+
EQYDVQQKDApDb3JlT1MgSW5jMRQwEgYDVQQLDAtFbmdpbmVlcmluZzETMBEGA1UE
54+
AwwKY29yZW9zLmNvbTEdMBsGCSqGSIb3DQEJARYOb2VtQGNvcmVvcy5jb20wdjAQ
55+
BgcqhkjOPQIBBgUrgQQAIgNiAAQDEhfHEulYKlANw9eR5l455gwzAIQuraa049Rh
56+
vM7PPywaiD8DobteQmE8wn7cJSzOYw6GLvrL4Q1BO5EFUXknkW50t8lfnUeHveCN
57+
sqvm82F1NVevVoExAUhDYmMREa6jZDBiMA8GA1UdEQQIMAaHBH8AAAEwHQYDVR0O
58+
BBYEFEbFy0SPiF1YXt+9T3Jig2rNmBtpMB8GA1UdIwQYMBaAFEbFy0SPiF1YXt+9
59+
T3Jig2rNmBtpMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDaQAwZgIxAOul
60+
t3MhI02IONjTDusl2YuCxMgpy2uy0MPkEGUHnUOsxmPSG0gEBCNHyeKVeTaPUwIx
61+
AKbyaAqbChEy9CvDgyv6qxTYU+eeBImLKS3PH2uW5etc/69V/sDojqpH3hEffsOt
62+
9g==
63+
-----END CERTIFICATE-----`)
64+
65+
// PrivateKey2 is generated via
66+
// openssl ecparam -genkey -name secp384r1 -out server.key
67+
PrivateKey2 = []byte(`-----BEGIN EC PARAMETERS-----
68+
BgUrgQQAIg==
69+
-----END EC PARAMETERS-----
70+
-----BEGIN EC PRIVATE KEY-----
71+
MIGkAgEBBDCfXncsl/kqihUWRHThBdGEDpv/bavwHYEi2tjrHiRkm+b7zhFlup8o
72+
aP1l1zP1LhKgBwYFK4EEACKhZANiAAQ/J0D0C3h2a55JU3/EANe1d3e2/mfcoXGq
73+
P8soiFdYntRIC4+V4dnRJuHRR+FHR/3531EIf2WXsoIJr/IRhR/j0tAeXpZ++G+E
74+
vaooXf7gShnhRYKM4viPx4+DhSPjmqw=
75+
-----END EC PRIVATE KEY-----`)
76+
77+
// PublicKey2 is generate via:
78+
// generate csr:
79+
// openssl req -new -key server.key -out server.csr
80+
// generate certificate:
81+
// openssl x509 -req -days 3650 -in server.csr -signkey server.key -out
82+
// server.crt -extensions v3_req -extfile extfile.conf
83+
// where extfile.conf has the following details:
84+
// $ cat extfile.conf
85+
// [ v3_req ]
86+
// subjectAltName = IP:127.0.0.1
87+
// subjectKeyIdentifier=hash
88+
// authorityKeyIdentifier=keyid
89+
// basicConstraints = critical,CA:TRUE
90+
PublicKey2 = []byte(`-----BEGIN CERTIFICATE-----
91+
MIICrDCCAjOgAwIBAgIUbFS1ugcEYYGQoTiV6O//r3wdO58wCgYIKoZIzj0EAwIw
92+
gYQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEQMA4GA1UEBwwHUmFsZWlnaDEQ
93+
MA4GA1UECgwHUmVkIEhhdDEUMBIGA1UECwwLRW5naW5lZXJpbmcxDzANBgNVBAMM
94+
BkNvcmVPUzEdMBsGCSqGSIb3DQEJARYOb2VtQGNvcmVvcy5jb20wHhcNMjAwNTA3
95+
MjIzMzA3WhcNMzAwNTA1MjIzMzA3WjCBhDELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
96+
Ak5DMRAwDgYDVQQHDAdSYWxlaWdoMRAwDgYDVQQKDAdSZWQgSGF0MRQwEgYDVQQL
97+
DAtFbmdpbmVlcmluZzEPMA0GA1UEAwwGQ29yZU9TMR0wGwYJKoZIhvcNAQkBFg5v
98+
ZW1AY29yZW9zLmNvbTB2MBAGByqGSM49AgEGBSuBBAAiA2IABD8nQPQLeHZrnklT
99+
f8QA17V3d7b+Z9yhcao/yyiIV1ie1EgLj5Xh2dEm4dFH4UdH/fnfUQh/ZZeyggmv
100+
8hGFH+PS0B5eln74b4S9qihd/uBKGeFFgozi+I/Hj4OFI+OarKNkMGIwDwYDVR0R
101+
BAgwBocEfwAAATAdBgNVHQ4EFgQUovVgWNFFPhrF7XzaRteDnpfPXxowHwYDVR0j
102+
BBgwFoAUovVgWNFFPhrF7XzaRteDnpfPXxowDwYDVR0TAQH/BAUwAwEB/zAKBggq
103+
hkjOPQQDAgNnADBkAjBvCIr9k43oR18Z4HLTzaRfzacFzo75Lt5n0pk3PA5CrUg3
104+
sXU6o4IxyLNFHzIJn7cCMGTMVKEzoSZDclxkEgu53WM7PQljHgL9FJScEt4hzO2u
105+
FFNjhq0ODV1LNc1i8pQCAg==
106+
-----END CERTIFICATE-----`)
107+
// CABundle is a combination of PublicKey + PublicKey2.
108+
CABundle = append(append(PublicKey, '\n'), PublicKey2...)
109+
)

tests/negative/security/tls.go

+73-40
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ import (
2222
"net/http"
2323
"net/http/httptest"
2424

25+
"github.com/coreos/ignition/v2/tests/fixtures"
2526
"github.com/coreos/ignition/v2/tests/register"
2627
"github.com/coreos/ignition/v2/tests/types"
2728
)
2829

2930
func init() {
30-
cer, err := tls.X509KeyPair(publicKey, privateKey)
31+
cer, err := tls.X509KeyPair(fixtures.PublicKey, fixtures.PrivateKey)
3132
if err != nil {
3233
panic(fmt.Sprintf("error loading x509 keypair: %v", err))
3334
}
@@ -36,46 +37,23 @@ func init() {
3637
customCAServer.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
3738
customCAServer.StartTLS()
3839

40+
cer2, err := tls.X509KeyPair(fixtures.PublicKey2, fixtures.PrivateKey2)
41+
if err != nil {
42+
panic(fmt.Sprintf("error loading x509 keypair2: %v", err))
43+
}
44+
config2 := &tls.Config{Certificates: []tls.Certificate{cer2}}
45+
customCAServer2.TLS = config2
46+
customCAServer2.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
47+
customCAServer2.StartTLS()
48+
3949
register.Register(register.NegativeTest, AppendConfigCustomCert())
40-
register.Register(register.NegativeTest, AppendConfigCustomCertHTTP())
41-
register.Register(register.NegativeTest, AppendConfigCustomCertInvalidHeaderHTTP())
50+
register.Register(register.NegativeTest, FetchFileCustomCertHTTP())
51+
register.Register(register.NegativeTest, FetchFileCABundleCertHTTP())
52+
register.Register(register.NegativeTest, FetchFileCustomCertInvalidHeaderHTTP())
4253
register.Register(register.NegativeTest, FetchFileCustomCert())
4354
}
4455

4556
var (
46-
// generated via:
47-
// openssl ecparam -genkey -name secp384r1 -out server.key
48-
privateKey = []byte(`-----BEGIN EC PARAMETERS-----
49-
BgUrgQQAIg==
50-
-----END EC PARAMETERS-----
51-
-----BEGIN EC PRIVATE KEY-----
52-
MIGkAgEBBDB6yW6RIYfTXdYVuPY0V0L6EtZ6vZD86vgbsw52Y3/U5nZ2JE++JrKu
53-
tt2Xt/NMzG6gBwYFK4EEACKhZANiAAQDEhfHEulYKlANw9eR5l455gwzAIQuraa0
54-
49RhvM7PPywaiD8DobteQmE8wn7cJSzOYw6GLvrL4Q1BO5EFUXknkW50t8lfnUeH
55-
veCNsqvm82F1NVevVoExAUhDYmMREa4=
56-
-----END EC PRIVATE KEY-----`)
57-
58-
// generated via:
59-
// openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
60-
publicKey = []byte(`-----BEGIN CERTIFICATE-----
61-
MIICzTCCAlKgAwIBAgIJALTP0pfNBMzGMAoGCCqGSM49BAMCMIGZMQswCQYDVQQG
62-
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj
63-
bzETMBEGA1UECgwKQ29yZU9TIEluYzEUMBIGA1UECwwLRW5naW5lZXJpbmcxEzAR
64-
BgNVBAMMCmNvcmVvcy5jb20xHTAbBgkqhkiG9w0BCQEWDm9lbUBjb3Jlb3MuY29t
65-
MB4XDTE4MDEyNTAwMDczOVoXDTI4MDEyMzAwMDczOVowgZkxCzAJBgNVBAYTAlVT
66-
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMw
67-
EQYDVQQKDApDb3JlT1MgSW5jMRQwEgYDVQQLDAtFbmdpbmVlcmluZzETMBEGA1UE
68-
AwwKY29yZW9zLmNvbTEdMBsGCSqGSIb3DQEJARYOb2VtQGNvcmVvcy5jb20wdjAQ
69-
BgcqhkjOPQIBBgUrgQQAIgNiAAQDEhfHEulYKlANw9eR5l455gwzAIQuraa049Rh
70-
vM7PPywaiD8DobteQmE8wn7cJSzOYw6GLvrL4Q1BO5EFUXknkW50t8lfnUeHveCN
71-
sqvm82F1NVevVoExAUhDYmMREa6jZDBiMA8GA1UdEQQIMAaHBH8AAAEwHQYDVR0O
72-
BBYEFEbFy0SPiF1YXt+9T3Jig2rNmBtpMB8GA1UdIwQYMBaAFEbFy0SPiF1YXt+9
73-
T3Jig2rNmBtpMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDaQAwZgIxAOul
74-
t3MhI02IONjTDusl2YuCxMgpy2uy0MPkEGUHnUOsxmPSG0gEBCNHyeKVeTaPUwIx
75-
AKbyaAqbChEy9CvDgyv6qxTYU+eeBImLKS3PH2uW5etc/69V/sDojqpH3hEffsOt
76-
9g==
77-
-----END CERTIFICATE-----`)
78-
7957
customCAServerFile = []byte(`{
8058
"ignition": { "version": "3.0.0" },
8159
"storage": {
@@ -85,10 +63,22 @@ AKbyaAqbChEy9CvDgyv6qxTYU+eeBImLKS3PH2uW5etc/69V/sDojqpH3hEffsOt
8563
}]
8664
}
8765
}`)
66+
customCAServerFile2 = []byte(`{
67+
"ignition": { "version": "3.0.0" },
68+
"storage": {
69+
"files": [{
70+
"path": "/foo/bar2",
71+
"contents": { "source": "data:,example%20file2%0A" }
72+
}]
73+
}
74+
}`)
8875

8976
customCAServer = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
9077
w.Write(customCAServerFile)
9178
}))
79+
customCAServer2 = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
80+
w.Write(customCAServerFile2)
81+
}))
9282
)
9383

9484
func AppendConfigCustomCert() types.Test {
@@ -150,8 +140,8 @@ func FetchFileCustomCert() types.Test {
150140
}
151141
}
152142

153-
func AppendConfigCustomCertHTTP() types.Test {
154-
name := "tls.config.merge.http"
143+
func FetchFileCustomCertHTTP() types.Test {
144+
name := "tls.fetchfile.http"
155145
in := types.GetBaseDisk()
156146
out := types.GetBaseDisk()
157147
config := fmt.Sprintf(`{
@@ -185,8 +175,51 @@ func AppendConfigCustomCertHTTP() types.Test {
185175
}
186176
}
187177

188-
func AppendConfigCustomCertInvalidHeaderHTTP() types.Test {
189-
name := "tls.config.merge.http.invalidheader"
178+
// FetchFileCABundleCertHTTP fetches the ignition configs hosted
179+
// on the TLS servers using a CA bundle that includes only the first
180+
// server's CA key.
181+
func FetchFileCABundleCertHTTP() types.Test {
182+
name := "tls.fetchfile.http.cabundle"
183+
in := types.GetBaseDisk()
184+
out := types.GetBaseDisk()
185+
config := fmt.Sprintf(`{
186+
"ignition": {
187+
"version": "$version",
188+
"security": {
189+
"tls": {
190+
"certificateAuthorities": [{
191+
"source": "http://127.0.0.1:8080/certificates"
192+
}]
193+
}
194+
}
195+
},
196+
"storage": {
197+
"files": [{
198+
"path": "/foo/bar",
199+
"contents": {
200+
"source": %q
201+
}
202+
},{
203+
"path": "/foo/bar2",
204+
"contents": {
205+
"source": %q
206+
}
207+
}]
208+
}
209+
}`, customCAServer.URL, customCAServer2.URL)
210+
configMinVersion := "3.0.0"
211+
212+
return types.Test{
213+
Name: name,
214+
In: in,
215+
Out: out,
216+
Config: config,
217+
ConfigMinVersion: configMinVersion,
218+
}
219+
}
220+
221+
func FetchFileCustomCertInvalidHeaderHTTP() types.Test {
222+
name := "tls.fetchfile.http.invalidheader"
190223
in := types.GetBaseDisk()
191224
out := types.GetBaseDisk()
192225
config := fmt.Sprintf(`{

0 commit comments

Comments
 (0)