Skip to content

Commit

Permalink
Merge pull request #983 from sohankunkerkar/backport-caBundle
Browse files Browse the repository at this point in the history
[spec2x] Backporting the CA bundles support
  • Loading branch information
sohankunkerkar committed May 19, 2020
2 parents 73ff2d7 + 1695f7b commit b4d18ad
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 118 deletions.
2 changes: 1 addition & 1 deletion doc/configuration-v2_2.md
Expand Up @@ -19,7 +19,7 @@ The Ignition configuration is a JSON document conforming to the following specif
* **_security_** (object): options relating to network security.
* **_tls_** (object): options relating to TLS when fetching resources over `https`.
* **_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`; this applies only to Ignition itself, the certificates are not added persistently to the system-wide trust store.
* **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.
* **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.
* **_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.
* **_storage_** (object): describes the desired state of the system's storage devices.
Expand Down
2 changes: 1 addition & 1 deletion doc/configuration-v2_3.md
Expand Up @@ -19,7 +19,7 @@ The Ignition configuration is a JSON document conforming to the following specif
* **_security_** (object): options relating to network security.
* **_tls_** (object): options relating to TLS when fetching resources over `https`.
* **_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`.
* **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.
* **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.
* **_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.
* **_storage_** (object): describes the desired state of the system's storage devices.
Expand Down
2 changes: 1 addition & 1 deletion doc/configuration-v2_4.md
Expand Up @@ -25,7 +25,7 @@ The Ignition configuration is a JSON document conforming to the following specif
* **_security_** (object): options relating to network security.
* **_tls_** (object): options relating to TLS when fetching resources over `https`.
* **_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`.
* **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.
* **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.
* **_httpHeaders_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only.
* **name** (string): the header name.
* **value** (string): the header contents.
Expand Down
2 changes: 1 addition & 1 deletion doc/configuration-v2_5-experimental.md
Expand Up @@ -27,7 +27,7 @@ The Ignition configuration is a JSON document conforming to the following specif
* **_security_** (object): options relating to network security.
* **_tls_** (object): options relating to TLS when fetching resources over `https`.
* **_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`.
* **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.
* **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.
* **_httpHeaders_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only.
* **name** (string): the header name.
* **value** (string): the header contents.
Expand Down
24 changes: 16 additions & 8 deletions internal/resource/http.go
Expand Up @@ -108,24 +108,32 @@ func (f *Fetcher) UpdateHttpTimeoutsAndCAs(timeouts types.Timeouts, cas []types.
if err != nil {
return err
}
block, _ := pem.Decode(cablob)
if err := f.parseCABundle(cablob, ca, pool); err != nil {
f.Logger.Err("Unable to parse CA bundle: %s", err)
return err
}
}
f.client.transport.TLSClientConfig = &tls.Config{RootCAs: pool}
return nil
}

// parseCABundle parses a CA bundle which includes multiple CAs.
func (f *Fetcher) parseCABundle(cablob []byte, ca types.CaReference, pool *x509.CertPool) error {
for len(cablob) > 0 {
block, rest := pem.Decode(cablob)
if block == nil {
f.Logger.Err("Unable to decode CA (%s)", ca.Source)
f.Logger.Err("Unable to decode CA (%v)", ca.Source)
return ErrPEMDecodeFailed
}

cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
f.Logger.Err("Unable to parse CA (%s): %s", ca.Source, err)
f.Logger.Err("Unable to parse CA (%v): %s", ca.Source, err)
return err
}

f.Logger.Info("Adding %q to list of CAs", cert.Subject.CommonName)
pool.AddCert(cert)
cablob = rest
}

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

return nil
}

Expand Down
109 changes: 109 additions & 0 deletions tests/fixtures/tls_fixture.go
@@ -0,0 +1,109 @@
// Copyright 2020 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 fixtures

// This file contains all the large constants used for testing
// the tls flow.

var (
// PrivateKey is generated via:
// openssl ecparam -genkey -name secp384r1 -out server.key
PrivateKey = []byte(`-----BEGIN EC PARAMETERS-----
BgUrgQQAIg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDB6yW6RIYfTXdYVuPY0V0L6EtZ6vZD86vgbsw52Y3/U5nZ2JE++JrKu
tt2Xt/NMzG6gBwYFK4EEACKhZANiAAQDEhfHEulYKlANw9eR5l455gwzAIQuraa0
49RhvM7PPywaiD8DobteQmE8wn7cJSzOYw6GLvrL4Q1BO5EFUXknkW50t8lfnUeH
veCNsqvm82F1NVevVoExAUhDYmMREa4=
-----END EC PRIVATE KEY-----`)

// PublicKey is generated via:
// generate csr:
// openssl req -new -key server.key -out server.csr
// generate certificate:
// openssl x509 -req -days 3650 -in server.csr -signkey server.key -out
// server.crt -extensions v3_req -extfile extfile.conf
// where extfile.conf has the following details:
// $ cat extfile.conf
// [ v3_req ]
// subjectAltName = IP:127.0.0.1
// subjectKeyIdentifier=hash
// authorityKeyIdentifier=keyid
// basicConstraints = critical,CA:TRUE
PublicKey = []byte(`-----BEGIN CERTIFICATE-----
MIICzTCCAlKgAwIBAgIJALTP0pfNBMzGMAoGCCqGSM49BAMCMIGZMQswCQYDVQQG
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj
bzETMBEGA1UECgwKQ29yZU9TIEluYzEUMBIGA1UECwwLRW5naW5lZXJpbmcxEzAR
BgNVBAMMCmNvcmVvcy5jb20xHTAbBgkqhkiG9w0BCQEWDm9lbUBjb3Jlb3MuY29t
MB4XDTE4MDEyNTAwMDczOVoXDTI4MDEyMzAwMDczOVowgZkxCzAJBgNVBAYTAlVT
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMw
EQYDVQQKDApDb3JlT1MgSW5jMRQwEgYDVQQLDAtFbmdpbmVlcmluZzETMBEGA1UE
AwwKY29yZW9zLmNvbTEdMBsGCSqGSIb3DQEJARYOb2VtQGNvcmVvcy5jb20wdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAAQDEhfHEulYKlANw9eR5l455gwzAIQuraa049Rh
vM7PPywaiD8DobteQmE8wn7cJSzOYw6GLvrL4Q1BO5EFUXknkW50t8lfnUeHveCN
sqvm82F1NVevVoExAUhDYmMREa6jZDBiMA8GA1UdEQQIMAaHBH8AAAEwHQYDVR0O
BBYEFEbFy0SPiF1YXt+9T3Jig2rNmBtpMB8GA1UdIwQYMBaAFEbFy0SPiF1YXt+9
T3Jig2rNmBtpMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDaQAwZgIxAOul
t3MhI02IONjTDusl2YuCxMgpy2uy0MPkEGUHnUOsxmPSG0gEBCNHyeKVeTaPUwIx
AKbyaAqbChEy9CvDgyv6qxTYU+eeBImLKS3PH2uW5etc/69V/sDojqpH3hEffsOt
9g==
-----END CERTIFICATE-----`)

// PrivateKey2 is generated via
// openssl ecparam -genkey -name secp384r1 -out server.key
PrivateKey2 = []byte(`-----BEGIN EC PARAMETERS-----
BgUrgQQAIg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDCfXncsl/kqihUWRHThBdGEDpv/bavwHYEi2tjrHiRkm+b7zhFlup8o
aP1l1zP1LhKgBwYFK4EEACKhZANiAAQ/J0D0C3h2a55JU3/EANe1d3e2/mfcoXGq
P8soiFdYntRIC4+V4dnRJuHRR+FHR/3531EIf2WXsoIJr/IRhR/j0tAeXpZ++G+E
vaooXf7gShnhRYKM4viPx4+DhSPjmqw=
-----END EC PRIVATE KEY-----`)

// PublicKey2 is generate via:
// generate csr:
// openssl req -new -key server.key -out server.csr
// generate certificate:
// openssl x509 -req -days 3650 -in server.csr -signkey server.key -out
// server.crt -extensions v3_req -extfile extfile.conf
// where extfile.conf has the following details:
// $ cat extfile.conf
// [ v3_req ]
// subjectAltName = IP:127.0.0.1
// subjectKeyIdentifier=hash
// authorityKeyIdentifier=keyid
// basicConstraints = critical,CA:TRUE
PublicKey2 = []byte(`-----BEGIN CERTIFICATE-----
MIICrDCCAjOgAwIBAgIUbFS1ugcEYYGQoTiV6O//r3wdO58wCgYIKoZIzj0EAwIw
gYQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEQMA4GA1UEBwwHUmFsZWlnaDEQ
MA4GA1UECgwHUmVkIEhhdDEUMBIGA1UECwwLRW5naW5lZXJpbmcxDzANBgNVBAMM
BkNvcmVPUzEdMBsGCSqGSIb3DQEJARYOb2VtQGNvcmVvcy5jb20wHhcNMjAwNTA3
MjIzMzA3WhcNMzAwNTA1MjIzMzA3WjCBhDELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
Ak5DMRAwDgYDVQQHDAdSYWxlaWdoMRAwDgYDVQQKDAdSZWQgSGF0MRQwEgYDVQQL
DAtFbmdpbmVlcmluZzEPMA0GA1UEAwwGQ29yZU9TMR0wGwYJKoZIhvcNAQkBFg5v
ZW1AY29yZW9zLmNvbTB2MBAGByqGSM49AgEGBSuBBAAiA2IABD8nQPQLeHZrnklT
f8QA17V3d7b+Z9yhcao/yyiIV1ie1EgLj5Xh2dEm4dFH4UdH/fnfUQh/ZZeyggmv
8hGFH+PS0B5eln74b4S9qihd/uBKGeFFgozi+I/Hj4OFI+OarKNkMGIwDwYDVR0R
BAgwBocEfwAAATAdBgNVHQ4EFgQUovVgWNFFPhrF7XzaRteDnpfPXxowHwYDVR0j
BBgwFoAUovVgWNFFPhrF7XzaRteDnpfPXxowDwYDVR0TAQH/BAUwAwEB/zAKBggq
hkjOPQQDAgNnADBkAjBvCIr9k43oR18Z4HLTzaRfzacFzo75Lt5n0pk3PA5CrUg3
sXU6o4IxyLNFHzIJn7cCMGTMVKEzoSZDclxkEgu53WM7PQljHgL9FJScEt4hzO2u
FFNjhq0ODV1LNc1i8pQCAg==
-----END CERTIFICATE-----`)
// CABundle is a combination of PublicKey + PublicKey2.
CABundle = append(append(PublicKey, '\n'), PublicKey2...)
)
120 changes: 78 additions & 42 deletions tests/negative/security/tls.go
Expand Up @@ -22,12 +22,13 @@ import (
"net/http"
"net/http/httptest"

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

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

cer2, err := tls.X509KeyPair(fixtures.PublicKey2, fixtures.PrivateKey2)
if err != nil {
panic(fmt.Sprintf("error loading x509 keypair2: %v", err))
}
config2 := &tls.Config{Certificates: []tls.Certificate{cer2}}
customCAServer2.TLS = config2
customCAServer2.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
customCAServer2.StartTLS()

register.Register(register.NegativeTest, AppendConfigCustomCert())
register.Register(register.NegativeTest, FetchFileCustomCert())
register.Register(register.NegativeTest, AppendConfigCustomCertHTTP())
register.Register(register.NegativeTest, AppendConfigCustomCertInvalidHeaderHTTP())
register.Register(register.NegativeTest, FetchFileCABundleCertHTTP())
register.Register(register.NegativeTest, FetchFileCustomCertHTTP())
register.Register(register.NegativeTest, FetchFileCustomCertInvalidHeaderHTTP())
}

var (
// generated via:
// openssl ecparam -genkey -name secp384r1 -out server.key
privateKey = []byte(`-----BEGIN EC PARAMETERS-----
BgUrgQQAIg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDB6yW6RIYfTXdYVuPY0V0L6EtZ6vZD86vgbsw52Y3/U5nZ2JE++JrKu
tt2Xt/NMzG6gBwYFK4EEACKhZANiAAQDEhfHEulYKlANw9eR5l455gwzAIQuraa0
49RhvM7PPywaiD8DobteQmE8wn7cJSzOYw6GLvrL4Q1BO5EFUXknkW50t8lfnUeH
veCNsqvm82F1NVevVoExAUhDYmMREa4=
-----END EC PRIVATE KEY-----`)

// generated via:
// openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
publicKey = []byte(`-----BEGIN CERTIFICATE-----
MIICzTCCAlKgAwIBAgIJALTP0pfNBMzGMAoGCCqGSM49BAMCMIGZMQswCQYDVQQG
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj
bzETMBEGA1UECgwKQ29yZU9TIEluYzEUMBIGA1UECwwLRW5naW5lZXJpbmcxEzAR
BgNVBAMMCmNvcmVvcy5jb20xHTAbBgkqhkiG9w0BCQEWDm9lbUBjb3Jlb3MuY29t
MB4XDTE4MDEyNTAwMDczOVoXDTI4MDEyMzAwMDczOVowgZkxCzAJBgNVBAYTAlVT
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMw
EQYDVQQKDApDb3JlT1MgSW5jMRQwEgYDVQQLDAtFbmdpbmVlcmluZzETMBEGA1UE
AwwKY29yZW9zLmNvbTEdMBsGCSqGSIb3DQEJARYOb2VtQGNvcmVvcy5jb20wdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAAQDEhfHEulYKlANw9eR5l455gwzAIQuraa049Rh
vM7PPywaiD8DobteQmE8wn7cJSzOYw6GLvrL4Q1BO5EFUXknkW50t8lfnUeHveCN
sqvm82F1NVevVoExAUhDYmMREa6jZDBiMA8GA1UdEQQIMAaHBH8AAAEwHQYDVR0O
BBYEFEbFy0SPiF1YXt+9T3Jig2rNmBtpMB8GA1UdIwQYMBaAFEbFy0SPiF1YXt+9
T3Jig2rNmBtpMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDaQAwZgIxAOul
t3MhI02IONjTDusl2YuCxMgpy2uy0MPkEGUHnUOsxmPSG0gEBCNHyeKVeTaPUwIx
AKbyaAqbChEy9CvDgyv6qxTYU+eeBImLKS3PH2uW5etc/69V/sDojqpH3hEffsOt
9g==
-----END CERTIFICATE-----`)

customCAServerFile = []byte(`{
"ignition": { "version": "2.0.0" },
"storage": {
Expand All @@ -87,13 +65,26 @@ AKbyaAqbChEy9CvDgyv6qxTYU+eeBImLKS3PH2uW5etc/69V/sDojqpH3hEffsOt
}
}`)

customCAServerFile2 = []byte(`{
"ignition": { "version": "2.0.0" },
"storage": {
"files": [{
"filesystem": "root",
"path": "/foo/bar2",
"contents": { "source": "data:,example%20file2%0A" }
}]
}
}`)
customCAServer = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(customCAServerFile)
}))
customCAServer2 = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(customCAServerFile2)
}))
)

func AppendConfigCustomCert() types.Test {
name := "Append config with custom tls cert"
name := "tls.config.merge.needsca"
in := types.GetBaseDisk()
out := types.GetBaseDisk()
config := fmt.Sprintf(`{
Expand Down Expand Up @@ -121,7 +112,7 @@ func AppendConfigCustomCert() types.Test {
}

func FetchFileCustomCert() types.Test {
name := "Fetch file with custom tls cert"
name := "tls.file.create.needsca"
in := types.GetBaseDisk()
out := types.GetBaseDisk()
config := fmt.Sprintf(`{
Expand Down Expand Up @@ -152,8 +143,53 @@ func FetchFileCustomCert() types.Test {
}
}

func AppendConfigCustomCertHTTP() types.Test {
name := "Fetch Certificate from Invalid Address"
// FetchFileCABundleCertHTTP fetches the ignition configs hosted
// on the TLS servers using a CA bundle that includes only the first
// server's CA key.
func FetchFileCABundleCertHTTP() types.Test {
name := "tls.fetchfile.http.cabundle"
in := types.GetBaseDisk()
out := types.GetBaseDisk()
config := fmt.Sprintf(`{
"ignition": {
"version": "$version",
"security": {
"tls": {
"certificateAuthorities": [{
"source": "http://127.0.0.1:8080/certificates"
}]
}
}
},
"storage": {
"files": [{
"filesystem": "root",
"path": "/foo/bar",
"contents": {
"source": %q
}
},{
"filesystem": "root",
"path": "/foo/bar2",
"contents": {
"source": %q
}
}]
}
}`, customCAServer.URL, customCAServer2.URL)
configMinVersion := "2.2.0"

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

func FetchFileCustomCertHTTP() types.Test {
name := "tls.fetchfile.http"
in := types.GetBaseDisk()
out := types.GetBaseDisk()
config := fmt.Sprintf(`{
Expand Down Expand Up @@ -188,8 +224,8 @@ func AppendConfigCustomCertHTTP() types.Test {
}
}

func AppendConfigCustomCertInvalidHeaderHTTP() types.Test {
name := "Fetch Certificate with Invalid Header - HTTP"
func FetchFileCustomCertInvalidHeaderHTTP() types.Test {
name := "tls.fetchfile.http.invalidheader"
in := types.GetBaseDisk()
out := types.GetBaseDisk()
config := fmt.Sprintf(`{
Expand Down

0 comments on commit b4d18ad

Please sign in to comment.