Skip to content
Merged
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
18 changes: 18 additions & 0 deletions runtime/tls/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
Copyright 2021 The Flux authors

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 tls contains helpers to convert Kubernetes secrets to TLS certificates.
package tls
51 changes: 51 additions & 0 deletions runtime/tls/testdata/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Copyright 2021 The Flux authors

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 testdata

var ExampleCA = []byte(`-----BEGIN CERTIFICATE-----
MIIB7TCCAZKgAwIBAgIUB+17B8PU05wVTzRHLeG+S+ybZK4wCgYIKoZIzj0EAwIw
GTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjAwNDE3MDgxODAwWhcNMzAw
NDE1MDgxODAwWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEG
CCqGSM49AwEHA0IABPud6ARpa71VE3pUhmI4vBjR1YVYPIzrNT3ni7lwiGY0JnLR
yxRKt3xPphsQi/dzLaWM5cCFgX9Ju6RBkK3um86jgbowgbcwDgYDVR0PAQH/BAQD
AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA
MB0GA1UdDgQWBBTM8HS5EIlVMBYv/300jN8PEArUgDAfBgNVHSMEGDAWgBQGyUiU
1QEZiMAqjsnIYTwZ4yp5wzA4BgNVHREEMTAvgglsb2NhbGhvc3SCC2V4YW1wbGUu
Y29tgg93d3cuZXhhbXBsZS5jb22HBH8AAAEwCgYIKoZIzj0EAwIDSQAwRgIhAOgB
5W82FEgiTTOmsNRekkK5jUPbj4D4eHtb2/BI7ph4AiEA2AxHASIFBdv5b7Qf5prb
bdNmUCzAvVuCAKuMjg2OPrE=
-----END CERTIFICATE-----`)

var ExampleCert = []byte(`-----BEGIN CERTIFICATE-----
MIIB7TCCAZKgAwIBAgIUB+17B8PU05wVTzRHLeG+S+ybZK4wCgYIKoZIzj0EAwIw
GTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjAwNDE3MDgxODAwWhcNMzAw
NDE1MDgxODAwWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEG
CCqGSM49AwEHA0IABPud6ARpa71VE3pUhmI4vBjR1YVYPIzrNT3ni7lwiGY0JnLR
yxRKt3xPphsQi/dzLaWM5cCFgX9Ju6RBkK3um86jgbowgbcwDgYDVR0PAQH/BAQD
AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA
MB0GA1UdDgQWBBTM8HS5EIlVMBYv/300jN8PEArUgDAfBgNVHSMEGDAWgBQGyUiU
1QEZiMAqjsnIYTwZ4yp5wzA4BgNVHREEMTAvgglsb2NhbGhvc3SCC2V4YW1wbGUu
Y29tgg93d3cuZXhhbXBsZS5jb22HBH8AAAEwCgYIKoZIzj0EAwIDSQAwRgIhAOgB
5W82FEgiTTOmsNRekkK5jUPbj4D4eHtb2/BI7ph4AiEA2AxHASIFBdv5b7Qf5prb
bdNmUCzAvVuCAKuMjg2OPrE=
-----END CERTIFICATE-----`)

var ExampleKey = []byte(`-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKQbEXV6nljOHMmPrWVWQ+JrAE5wsbE9iMhfY7wlJgXOoAoGCCqGSM49
AwEHoUQDQgAE+53oBGlrvVUTelSGYji8GNHVhVg8jOs1PeeLuXCIZjQmctHLFEq3
fE+mGxCL93MtpYzlwIWBf0m7pEGQre6bzg==
-----END EC PRIVATE KEY-----`)
81 changes: 81 additions & 0 deletions runtime/tls/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright 2021 The Flux authors

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 tls

import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"

corev1 "k8s.io/api/core/v1"
)

const (
ClientCertIdentifier = "certFile"
ClientKeyIdentifier = "keyFile"
CACertIdentifier = "caFile"
)

// ConfigFromSecret returns a TLS config created from the content of the secret.
// An error is returned if the secret does not contain a ClientCertIdentifier and ClientKeyIdentifier, or a
// CACertIdentifier.
func ConfigFromSecret(certSecret *corev1.Secret) (*tls.Config, error) {
validSecret := false
tlsConfig := &tls.Config{}

clientCert, clientCertOk := certSecret.Data[ClientCertIdentifier]
clientKey, clientKeyOk := certSecret.Data[ClientKeyIdentifier]
if clientKeyOk != clientCertOk {
return nil, fmt.Errorf("found one of %s or %s, and expected both or neither", ClientCertIdentifier, ClientKeyIdentifier)
}
if clientCertOk && clientKeyOk {
validSecret = true
cert, err := tls.X509KeyPair(clientCert, clientKey)
if err != nil {
return nil, err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
}

if caCert, ok := certSecret.Data[CACertIdentifier]; ok {
validSecret = true
sysCerts, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
sysCerts.AppendCertsFromPEM(caCert)
tlsConfig.RootCAs = sysCerts
}

if !validSecret {
return nil, fmt.Errorf("no %s and %s, or %s found in secret", ClientCertIdentifier, ClientKeyIdentifier, CACertIdentifier)
}

return tlsConfig, nil
}

// TransportFromSecret returns a HTTP transport with a TLS config created from the content of the secret.
// An error is returned if the secret does not contain a ClientCertIdentifier and ClientKeyIdentifier, or a
// CACertIdentifier.
func TransportFromSecret(certSecret *corev1.Secret) (*http.Transport, error) {
tlsConfig, err := ConfigFromSecret(certSecret)
if err != nil {
return nil, err
}
return &http.Transport{TLSClientConfig: tlsConfig}, nil
}
110 changes: 110 additions & 0 deletions runtime/tls/tls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
Copyright 2021 The Flux authors

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 tls

import (
"crypto/tls"
"testing"

"github.com/fluxcd/pkg/runtime/tls/testdata"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
)

func TestCert_TlsConfigAll(t *testing.T) {
secret := &corev1.Secret{
Data: map[string][]byte{
CACertIdentifier: testdata.ExampleCA,
ClientCertIdentifier: testdata.ExampleCert,
ClientKeyIdentifier: testdata.ExampleKey,
},
}
tlsConfig, err := ConfigFromSecret(secret)
require.NoError(t, err)
cert, err := tls.X509KeyPair(testdata.ExampleCert, testdata.ExampleKey)
require.NoError(t, err)
require.Equal(t, tlsConfig.Certificates[0], cert)
}

func TestCert_TlsConfigNone(t *testing.T) {
secret := &corev1.Secret{
Data: map[string][]byte{},
}
tlsConfig, err := ConfigFromSecret(secret)
require.EqualError(t, err, "no certFile and keyFile, or caFile found in secret")
require.Nil(t, tlsConfig)
}

func TestCert_TlsConfigOnlyCa(t *testing.T) {
secret := &corev1.Secret{
Data: map[string][]byte{
CACertIdentifier: testdata.ExampleCA,
},
}
tlsConfig, err := ConfigFromSecret(secret)
require.NoError(t, err)
require.NotNil(t, tlsConfig)
}

func TestCert_TlsConfigOnlyClient(t *testing.T) {
secret := &corev1.Secret{
Data: map[string][]byte{
ClientCertIdentifier: testdata.ExampleCert,
ClientKeyIdentifier: testdata.ExampleKey,
},
}
tlsConfig, err := ConfigFromSecret(secret)
require.NoError(t, err)
require.NotNil(t, tlsConfig)
}

func TestCert_TlsConfigMissingKey(t *testing.T) {
secret := &corev1.Secret{
Data: map[string][]byte{
CACertIdentifier: testdata.ExampleCA,
ClientCertIdentifier: testdata.ExampleCert,
},
}
tlsConfig, err := ConfigFromSecret(secret)
require.EqualError(t, err, "found one of certFile or keyFile, and expected both or neither")
require.Nil(t, tlsConfig)
}

func TestCert_TlsConfigMissingCert(t *testing.T) {
secret := &corev1.Secret{
Data: map[string][]byte{
CACertIdentifier: testdata.ExampleCA,
ClientKeyIdentifier: testdata.ExampleKey,
},
}
tlsConfig, err := ConfigFromSecret(secret)
require.EqualError(t, err, "found one of certFile or keyFile, and expected both or neither")
require.Nil(t, tlsConfig)
}

func TestCert_Transport(t *testing.T) {
secret := &corev1.Secret{
Data: map[string][]byte{
CACertIdentifier: testdata.ExampleCA,
ClientCertIdentifier: testdata.ExampleCert,
ClientKeyIdentifier: testdata.ExampleKey,
},
}
transport, err := TransportFromSecret(secret)
require.NoError(t, err)
require.NotNil(t, transport.TLSClientConfig)
}