Skip to content

Commit

Permalink
Add custom unmarshaller for certs.Certificate
Browse files Browse the repository at this point in the history
  • Loading branch information
teodor-pripoae committed Mar 9, 2020
1 parent 575ad4c commit ca63f5f
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 1 deletion.
3 changes: 2 additions & 1 deletion certs/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
"encoding/hex"
"encoding/pem"
"fmt"
"github.com/sirupsen/logrus"
"strings"

"github.com/sirupsen/logrus"
)

// Certificate is a X509 certifcate / private key pair
Expand Down
4 changes: 4 additions & 0 deletions certs/fixtures/file.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ca:
cert: fixtures/k8s-ca-crt.pem
privateKey: fixtures/k8s-ca-key.pem
password: foobar
17 changes: 17 additions & 0 deletions certs/fixtures/k8s-ca-crt.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICxDCCAaygAwIBAgIIQ5RaS8gSnKIwDQYJKoZIhvcNAQELBQAwDjEMMAoGA1UE
AxMDazhzMB4XDTIwMDMwOTEzNTkwMVoXDTIxMDMwODE0MTQwMVowDjEMMAoGA1UE
AxMDazhzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAturUlexOo5VR
FpFliNGByHjM6zSZcegqkZNDiXlm7U38PesAvOLHBK/D1rLOxtgSV35OeINHue4N
DaFpx2GKrnxZqzSXIyc0G/R0ROFMGru/JjR6Rq5kZLsoTI1LmdX/BfqCZYX3icl8
qBQSAcmsr9NyWaR3UCuo8fwYJHI2CGpDqGaqWEirTy16JrOeBwnJTbnRISymECvY
JugwEneJmw9CK1EXibT6kdyBfUu1IG0xdqkk2wsSl+yJGHfHCXnD3NM78X6IuU3a
Ge/gEGBAgbZrUhoGhB9eHIvI+Yq+gzpoL0gL0oIPl0edOFqGa84nPV2NRklDjASy
JuSWMUrpywIDAQABoyYwJDAOBgNVHQ8BAf8EBAMCAqQwEgYDVR0TAQH/BAgwBgEB
/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAlNI7ccMv/iudSkUo/Qd/rW3CD0ZSNw/t
RiaoOAhv+UBrwihWxl+/tFydIFthyIK1bF2B4s8MManbRaIjEPmM5iKgz4SE/qjy
5otlHUqXxVGFesaT5CD37GAIiSZvf9hJSC8nRVJ7r9Iq/l8T8IhZWwjv6vivqQlU
YgR6Gbul+MnJkkK38V6vnCMaBRo29a8mSbFQErXk6niaVTxUPSNCggBXXbHmf9W0
OpSzi7xqcmE+PtRekFfoNCoKqLxesjNXJZP9OCtBK0T0w4rdzw83T/TB58Sv2fAo
CsD7Hign1e9TjjzjCcJjtAmyCKmQlL/HUOFSVq/RNvRjdd17Dk/DsQ==
-----END CERTIFICATE-----
30 changes: 30 additions & 0 deletions certs/fixtures/k8s-ca-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,06ba1b9fbd57bfac9d71d0520fc61eff

PHju67RTiquKLN1dc9kT/OCt0gTcO1QC/VDODfJczkFvjFqGs/gjLPD5Tr95FXga
Cs/1y2qi9lzQoWEkdqtlEIeEWscS/nJVCTopgsO4IpuNYeKpgL72O1GFv431JP9M
qO0SqMqawdMi+GCGlizmkTfwfAuxdX9r+dLSiXlaiTYfkulGiFICkSkyNN0KlV2S
OSZ1EOnAp3s8ovz/stRa8qPNz0dkkX2YyVSFIwjeImz8AbM+VKdYatYPtPw5Yn/x
P3eQXC+hC9K4ruyGAaQbdc3tPG9RxFWY6BMykteD5lJm066St0VxjMqBXl5mwuao
de7u057zsikESl5MuSW3jLeT+jKtNYpEz9C5qzE+cUOEODqDVygQhs5z9Iqo/gwU
MajRgZOQnmh3WXPgV0xhFfPGL5Q+7dYx3tw5IG0NHJAUGxAK0N79D06wVm8UjTfg
z2HpzXDGTJc2wM+oUIH3ES1N5H7L2ICzJvVlRJ/V5sW9AlEeha6BgfDcDsZrA6Xy
4qEng+kex0MwYfK2xsjaImWmQLhAGzPd6LHr+mjP8nH9z2o7bbZhJePjD0jpkvn+
NS2Zqa75DwhZ6jOAT8o4BWfixdSZsJcN2ezoV2A9cSM7O3DucpZX+Fw0Og6fo241
5Gm8CoDK9UCYqyq9AJUfMgQxZe2M3HlYG9WZBdmF59vMgGMwxsdtr1XW/lZ6+kMS
DCoNrwhcgzmV3x8r2VuoNUGdTYoZQbNkK1p/v+Jydt39WhGaahXfoVlquq6odWHI
whuCKjzjBBBQRw2eqC6pnlTuQsC/NRJVesN7cJfUnaTz6R6qUV+rBvfcnp/mQLIO
BvrhbVy6SF8t9ICoMZVAFRDiXREY7QYMohZGX+3nlLC5WeaCMSNh/oLR4di3NFmG
noAoDAfvhaZVDXxbpPmfo5u5JfXTUen1AnevB+NW3R3OMrVJw7gQB8STfsmSSw6g
s+vF/9Ri4emZHENQAMjz5mgMbFnOftJW89s+S0hhQX3qe4Bx0ICTo7j+qdZ0vrbW
VgGPcCBQGTLluTMzrlslvNAbh68tBge1OqjFTzQsgF3W86SBRZTn9FpIWVcNTA9D
ZLXpjrjH3rez8ZQgCzXrL/Le3MWy1JGWzLBPsxI5xN6WYZTciWXCdBebjnyjwDv1
XAXMl5vt9i5C3ftJksfhxf+irnqJrOpEYKApSWv4j12WzVqXK7/uU8IJF2qmtA0w
d9phS4JPZ0TsSUfDA7C3Lss5OE7X1oBs3+7J9ExYnhfINB196/ni/Vn79oHovDR6
BzlHUagxv3xcib1VBbKDDCJAuBYD476v+oU0IjY5ieoWNd/EDnWJWVWYEAwOxo5I
gshd77YQwFCJE4OC68s9ZoqmK76sz6pa3fiv0uUl8cEpe/jNdGOaVXr/rbZfQI41
AgduwW79QzRWnibmsjJg5w8/E4ZN+rvolZrbjZ4qWjBc3Ab7Ki8dpihcFiHo3Pdd
4Ojh5X+XOF4l2ZJQGUlsRzrMyqt3J4ZCogoZsMgwLOas4H0mnzbCk60ZewSk4sQr
Pu/Ud7rQRkJlwPoYMcwwk4yKagmjvxEC7/Lra6tvlCw7t6AyWiPIfGmKDQAvhsPe
-----END RSA PRIVATE KEY-----
53 changes: 53 additions & 0 deletions certs/fixtures/literal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
ca:
cert: |
-----BEGIN CERTIFICATE-----
MIIC/jCCAeagAwIBAgIIF/nAdnF01cAwDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UE
AxMgd2lsZGNhcmQubGl0ZXJhbC5mbGFua3NvdXJjZS5jb20wHhcNMjAwMzA5MTU1
OTQ5WhcNMjEwMzA4MTYxNDQ5WjArMSkwJwYDVQQDEyB3aWxkY2FyZC5saXRlcmFs
LmZsYW5rc291cmNlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMAZUJYyRCA+V2EVZPNrj/1iuc1mSgCTase3Zjd4rvJST5i4MZ9/+gRZMsvM4zUj
//XCajzIThSSbJ6SbWenB5P6GbDVs6pmiljszbHzt/XAnMJhNNWh9s5GsotHQD03
0XPjqeNiAoJ4dhG3kXa7VgJGo3CgKOyxXQa6qcy1RN+x4FGPQJ8AZCz7mIxeHwh1
+aZoYSoVpfgYJg/XfrxkpkMqD6w6G0Kas6YMomwYj1m4llIABuKnqtwBKgjWNYZa
8QHN9L/fxXRwA1UpSqFWPegr7hfRygkDh+NgUwy2Vh6OmTIR/thsQRjPyMqcJZZl
f5KkrlphUhqB9VRdpc71HwECAwEAAaMmMCQwDgYDVR0PAQH/BAQDAgKkMBIGA1Ud
EwEB/wQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAG3wWxaywIX/EYSkKbK0
ASIGCKg+egycs2cq3IgG9cQkrNhaAIP5qwzMLrMEq4yvNjiTE3qf9FTdXSkWU43m
nWvURNTpu22h4YBqE+rt2CPampLLfTNccpUjdIYPeBkc6AMBh+chiB58EHHra0Rx
u3N9EbYlK6GcBOq64c6OSG5igWqOueX58HItzlsPPEDmkvLP/MzQVBls9kPBcj/f
HIzZqq7Y0Dcyf1pATHxigLHMplee1jnd8y6YbMkquLiqvLZxPz1o9SqTcueJjMOC
wg6Cd1aQp0kCGuGlX3orJerQFYLQRMk6OtADoc+A8XB6rK6D/DN63OOXB4zLPa2d
t6o=
-----END CERTIFICATE-----
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,f99f5acaba229399088cc6d8ee0ad9d8
hTTCuDOZbKY8IMRh+FMQChBrGpkuZKEkItdqGSq2363GRnVoeNu4wWNRrod6beEC
B3X0EjOncUfhRd8kj20N6rczSMB+4nRwoVKSjUummoOH+duFFDiPL5RXpMQyaa/l
1okuFPB0oOc60Gxj5v1Y3dQ5R4sV3d4pMrVWHkHN7BMJOhIfH1wOM51yRSIIEEK2
iRaIx7x9Lw7xyjDuGZb2UKvildgEem2mUN/8bu7hsGOJpB964R9tkccVQxLrjxJ8
bJPiusnkBXA1ihT0UgfeiSQfmdPFmL0ZYLvUpsgPaPuW8vyMcINgIAORGLgsvLYk
GFlg7q4hE9PAGjPahfUASHlqlaXPMozo+RTCyIkqIreQpFSLPsVOwWpoBXsitLZg
sr9274BgFM5uQ/AwqnYe0ndHHhovqqPyU+x4DioWTiVQBYdTVJCm7EybWTqR+/e6
vOXvasdEbn/BZQVp93zubWrfRChFyOy9wu2xkIT9hginv3DCkeUeJCB1WDMl8+gR
PqJ4bAn7d7U+m9XdcerhN5hI/0Qfff3rwSbearCYE1JrgzYCrnl6PZX2hLYMz/1i
59Q1SF3wiQOkVmegZjOuctQnxHgPSR1Et5gmoGGOTbfeLB0iGWMGAM6CclaKFpJu
hhB4SPT96k8Ok1EterrqfTnRHBwYYeaX/dyA+AkTNY4aVNWThSEScaWgmfyEzrgG
u9/+7M5QnuvWlAk7P662+j7ohYQZEzgU7ZROTEy6qUBq/AdDOqjU8Cpg/vsXUpMp
APdHunMbMyAFjTIzDBhGZeIFbouWWq9QBKN2QYuYXlXnJvw4JBijRvsJZv9ZAia0
ZJy8t65jVzicWIf0wNWbpY7HenvadEXbrtWOAh+WLEt15VBf3BLHQF6MX6gLN6Uy
ztQeGjq/EA1ue+BGJrOwFjdSz6vMdXj/IC0MZTOQADcBT0y/XrxImrkLeaup2RiO
V88N2dB4UQr19mzmENgshili4qxe8NJ1KjQRllDLaKNq2aqtYYrKXOuWaYSC/P+g
qSvrSz2QtcI7a0w6kYggGbe2vb6NOyShTGYrmhuaEzRUEXqsGLDMVcXW+sqtk3Zx
zzKzbLCBMspCbicLGwh5KIvNrmKBY0vCm+elDJWHt7wso3YIBv1xTjnX586yKZwL
JDw0ga4aVTGD6eguOT9LTXgtheyl0SsE6LBQRRBvlXmNMFWvyyDoCpoXZD8KmTML
SrilY88iCiDufyFOG+NIklJrGibt7y/uworiSToFrlQHi7HGqSUWhUX05GgwIunf
1gWZaUNa98Y4xzCY3xOHgdYeP6ek/QqkS/7T/4mlWP0X7oxOfvdhZ2XYbIkhSrPq
A8LxVkbNwfYgnZjqOU6veYLVqGh4eCA0Q8zDG1ENsPMkPpDR8zy3SmqoA7CjaBA4
bSCoq6avg+xXFWXusWNv/hc7NyB3YdXK8aL4RojzQWuo0cIST2usyKY+lGgyee9J
8PFgluTQcMIKg2m7Ft13o1A4T/tq/8XhCdSN8XCVI+PwX8wSuSqvV21VHnfYKyF3
dx7YcFGd5pMb///PY/0hAJKsVz6AFUgSjRbEgAgrPhp6x1Hpg5npHUlHGcPbvhoZ
-----END RSA PRIVATE KEY-----
password: foobar
3 changes: 3 additions & 0 deletions certs/fixtures/remote.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ca:
cert: "https://raw.githubusercontent.com/grpc/grpc-java/v1.28.x/testing/src/main/resources/certs/server0.pem"
privateKey: "https://raw.githubusercontent.com/grpc/grpc-java/v1.28.x/testing/src/main/resources/certs/server0.key"
104 changes: 104 additions & 0 deletions certs/yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package certs

import (
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/pkg/errors"
)

const (
// CertificateHeader is the first line of a certificate file
CertificateHeader = "-----BEGIN CERTIFICATE-----"
// PrivateKeyHeader is the first line of a private key file
PrivateKeyHeader = "-----BEGIN RSA PRIVATE KEY-----"
)

type CertificateMarshaller struct {
CertFile string `yaml:"cert,omitempty"`
PrivateKeyFile string `yaml:"privateKey,omitempty"`
Password string `yaml:"password,omitempty"`
}

func (c *Certificate) UnmarshalYAML(unmarshal func(interface{}) error) error {
var cm CertificateMarshaller
if err := unmarshal(&cm); err != nil {
return err
}

cert, err := LoadCertificate(cm.CertFile)
if err != nil {
return errors.Wrap(err, "failed to load certificate")
}
privateKey, err := LoadPrivateKey(cm.PrivateKeyFile)
if err != nil {
return errors.Wrap(err, "failed to load private key")
}

password := loadPassword(cm.Password)

certificate, err := DecryptCertificate(cert, privateKey, []byte(password))
if err != nil {
return errors.Wrap(err, "failed to decrypt certificate")
}

*c = *certificate
return nil
}

func LoadCertificate(certificate string) ([]byte, error) {
if strings.HasPrefix(certificate, CertificateHeader) {
return []byte(certificate), nil
}

return loadCertificateBytes(certificate)
}

func LoadPrivateKey(privateKey string) ([]byte, error) {
if strings.HasPrefix(privateKey, PrivateKeyHeader) {
return []byte(privateKey), nil
}

return loadCertificateBytes(privateKey)
}

func loadCertificateBytes(certificate string) ([]byte, error) {
if strings.HasPrefix(certificate, "http") || strings.HasPrefix(certificate, "https") {
resp, err := http.Get(certificate)
if err != nil {
return nil, errors.Wrapf(err, "failed to download certificate from url %s", certificate)
}
defer resp.Body.Close()
certBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrapf(err, "failed to read response body from url %s", certificate)
}

return certBytes, nil
}

fullPath, err := filepath.Abs(certificate)
if err != nil {
return nil, errors.Wrap(err, "failed to expand path")
}

body, err := ioutil.ReadFile(fullPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to read certificate %s from disk", certificate)
}

return body, nil
}

func loadPassword(password string) string {
if strings.HasPrefix(password, "$") {
env := os.Getenv(password[1:])
if env != "" {
return env
}
}
return password
}
55 changes: 55 additions & 0 deletions certs/yaml_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package certs

import (
"io/ioutil"
"testing"

"github.com/pkg/errors"

. "github.com/onsi/gomega"
"gopkg.in/yaml.v2"
)

type exampleConfig struct {
CA Certificate `yaml:"ca"`
}

func TestLoadCertificateFromFiles(t *testing.T) {
g := NewWithT(t)
cfg, err := loadConfig("fixtures/file.yml")

g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.CA.X509.Subject.CommonName).To(Equal("k8s"))
}

func TestLoadCertificateFromURL(t *testing.T) {
g := NewWithT(t)
cfg, err := loadConfig("fixtures/remote.yml")

g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.CA.X509.Subject.CommonName).To(Equal("*.test.google.com.au"))
}

func TestLoadCertificateFromLiteral(t *testing.T) {
g := NewWithT(t)
cfg, err := loadConfig("fixtures/literal.yml")

g.Expect(err).ToNot(HaveOccurred())
g.Expect(cfg.CA.X509.Subject.CommonName).To(Equal("wildcard.literal.flanksource.com"))
}

func loadConfig(path string) (*exampleConfig, error) {
cfgBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "failed to read file %s", path)
}

cfg := exampleConfig{}
err = yaml.Unmarshal(cfgBytes, &cfg)

if err != nil {
return nil, errors.Wrapf(err, "failed to parse yml for file %s", path)
}

return &cfg, nil
}

0 comments on commit ca63f5f

Please sign in to comment.