cert-manager/test/util/util.go
James Munnelly e7fd05ddea Add e2e test verifying we can obtain ECDSA keys from RSA CA issuers
Signed-off-by: James Munnelly <james@munnelly.eu>
2018-11-07 11:14:54 +00:00

614 lines
20 KiB
Go

/*
Copyright 2018 The Jetstack cert-manager contributors.
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 util
// TODO: we should break this file apart into separate more sane/reusable parts
import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"fmt"
"time"
"github.com/golang/glog"
intscheme "github.com/jetstack/cert-manager/pkg/client/clientset/versioned/scheme"
"k8s.io/api/core/v1"
extv1beta1 "k8s.io/api/extensions/v1beta1"
apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
corecs "k8s.io/client-go/kubernetes/typed/core/v1"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
clientset "github.com/jetstack/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/pkg/util"
"github.com/jetstack/cert-manager/pkg/util/pki"
)
func CertificateOnlyValidForDomains(cert *x509.Certificate, commonName string, dnsNames ...string) bool {
if commonName != cert.Subject.CommonName || !util.EqualUnsorted(cert.DNSNames, dnsNames) {
return false
}
return true
}
func WaitForIssuerStatusFunc(client clientset.IssuerInterface, name string, fn func(*v1alpha1.Issuer) (bool, error)) error {
return wait.PollImmediate(500*time.Millisecond, wait.ForeverTestTimeout,
func() (bool, error) {
issuer, err := client.Get(name, metav1.GetOptions{})
if err != nil {
return false, fmt.Errorf("error getting Issuer %q: %v", name, err)
}
return fn(issuer)
})
}
// WaitForIssuerCondition waits for the status of the named issuer to contain
// a condition whose type and status matches the supplied one.
func WaitForIssuerCondition(client clientset.IssuerInterface, name string, condition v1alpha1.IssuerCondition) error {
pollErr := wait.PollImmediate(500*time.Millisecond, wait.ForeverTestTimeout,
func() (bool, error) {
glog.V(5).Infof("Waiting for issuer %v condition %#v", name, condition)
issuer, err := client.Get(name, metav1.GetOptions{})
if nil != err {
return false, fmt.Errorf("error getting Issuer %q: %v", name, err)
}
return issuer.HasCondition(condition), nil
},
)
return wrapErrorWithIssuerStatusCondition(client, pollErr, name, condition.Type)
}
// try to retrieve last condition to help diagnose tests.
func wrapErrorWithIssuerStatusCondition(client clientset.IssuerInterface, pollErr error, name string, conditionType v1alpha1.IssuerConditionType) error {
if pollErr == nil {
return nil
}
issuer, err := client.Get(name, metav1.GetOptions{})
if err != nil {
return pollErr
}
for _, cond := range issuer.GetStatus().Conditions {
if cond.Type == conditionType {
return fmt.Errorf("%s: Last Status: '%s' Reason: '%s', Message: '%s'", pollErr.Error(), cond.Status, cond.Reason, cond.Message)
}
}
return pollErr
}
// WaitForClusterIssuerCondition waits for the status of the named issuer to contain
// a condition whose type and status matches the supplied one.
func WaitForClusterIssuerCondition(client clientset.ClusterIssuerInterface, name string, condition v1alpha1.IssuerCondition) error {
pollErr := wait.PollImmediate(500*time.Millisecond, wait.ForeverTestTimeout,
func() (bool, error) {
glog.V(5).Infof("Waiting for clusterissuer %v condition %#v", name, condition)
issuer, err := client.Get(name, metav1.GetOptions{})
if nil != err {
return false, fmt.Errorf("error getting ClusterIssuer %v: %v", name, err)
}
return issuer.HasCondition(condition), nil
},
)
return wrapErrorWithClusterIssuerStatusCondition(client, pollErr, name, condition.Type)
}
// try to retrieve last condition to help diagnose tests.
func wrapErrorWithClusterIssuerStatusCondition(client clientset.ClusterIssuerInterface, pollErr error, name string, conditionType v1alpha1.IssuerConditionType) error {
if pollErr == nil {
return nil
}
issuer, err := client.Get(name, metav1.GetOptions{})
if err != nil {
return pollErr
}
for _, cond := range issuer.GetStatus().Conditions {
if cond.Type == conditionType {
return fmt.Errorf("%s: Last Status: '%s' Reason: '%s', Message: '%s'", pollErr.Error(), cond.Status, cond.Reason, cond.Message)
}
}
return pollErr
}
// WaitForCertificateCondition waits for the status of the named Certificate to contain
// a condition whose type and status matches the supplied one.
func WaitForCertificateCondition(client clientset.CertificateInterface, name string, condition v1alpha1.CertificateCondition, timeout time.Duration) error {
pollErr := wait.PollImmediate(500*time.Millisecond, timeout,
func() (bool, error) {
glog.V(5).Infof("Waiting for Certificate %v condition %#v", name, condition)
certificate, err := client.Get(name, metav1.GetOptions{})
if nil != err {
return false, fmt.Errorf("error getting Certificate %v: %v", name, err)
}
return certificate.HasCondition(condition), nil
},
)
return wrapErrorWithCertificateStatusCondition(client, pollErr, name, condition.Type)
}
// WaitForCertificateEvent waits for an event on the named Certificate to contain
// an event reason matches the supplied one.
func WaitForCertificateEvent(client kubernetes.Interface, cert *v1alpha1.Certificate, reason string, timeout time.Duration) error {
return wait.PollImmediate(500*time.Millisecond, timeout,
func() (bool, error) {
glog.V(5).Infof("Waiting for Certificate event %v reason %#v", cert.Name, reason)
evts, err := client.Core().Events(cert.Namespace).Search(intscheme.Scheme, cert)
if err != nil {
return false, fmt.Errorf("error getting Certificate %v: %v", cert.Name, err)
}
return hasEvent(evts, reason), nil
},
)
}
func hasEvent(events *v1.EventList, reason string) bool {
for _, evt := range events.Items {
if evt.Reason == reason {
return true
}
}
return false
}
// try to retrieve last condition to help diagnose tests.
func wrapErrorWithCertificateStatusCondition(client clientset.CertificateInterface, pollErr error, name string, conditionType v1alpha1.CertificateConditionType) error {
if pollErr == nil {
return nil
}
certificate, err := client.Get(name, metav1.GetOptions{})
if err != nil {
return pollErr
}
for _, cond := range certificate.Status.Conditions {
if cond.Type == conditionType {
return fmt.Errorf("%s: Last Status: '%s' Reason: '%s', Message: '%s'", pollErr.Error(), cond.Status, cond.Reason, cond.Message)
}
}
return pollErr
}
// WaitCertificateIssuedValid waits for the given Certificate to be
// 'Ready' and ensures the stored certificate is valid for the specified
// domains.
func WaitCertificateIssuedValid(certClient clientset.CertificateInterface, secretClient corecs.SecretInterface, name string, timeout time.Duration) error {
return wait.PollImmediate(time.Second, timeout,
func() (bool, error) {
glog.V(5).Infof("Waiting for Certificate %v to be ready", name)
certificate, err := certClient.Get(name, metav1.GetOptions{})
if err != nil {
return false, fmt.Errorf("error getting Certificate %v: %v", name, err)
}
isReady := certificate.HasCondition(v1alpha1.CertificateCondition{
Type: v1alpha1.CertificateConditionReady,
Status: v1alpha1.ConditionTrue,
})
if !isReady {
return false, nil
}
glog.Infof("Getting the TLS certificate Secret resource")
secret, err := secretClient.Get(certificate.Spec.SecretName, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return false, nil
}
return false, err
}
if !(len(secret.Data) == 2 || len(secret.Data) == 3) {
glog.Infof("Expected 2 keys in certificate secret, but there was %d", len(secret.Data))
return false, nil
}
keyBytes, ok := secret.Data[v1.TLSPrivateKeyKey]
if !ok {
glog.Infof("No private key data found for Certificate %q (secret %q)", name, certificate.Spec.SecretName)
return false, nil
}
key, err := pki.DecodePrivateKeyBytes(keyBytes)
if err != nil {
return false, err
}
// validate private key is of the correct type (rsa or ecdsa)
switch certificate.Spec.KeyAlgorithm {
case v1alpha1.KeyAlgorithm(""),
v1alpha1.RSAKeyAlgorithm:
_, ok := key.(*rsa.PrivateKey)
if !ok {
glog.Infof("Expected private key of type RSA, but it was: %T", key)
return false, nil
}
case v1alpha1.ECDSAKeyAlgorithm:
_, ok := key.(*ecdsa.PrivateKey)
if !ok {
glog.Infof("Expected private key of type ECDSA, but it was: %T", key)
return false, nil
}
default:
return false, fmt.Errorf("unrecognised requested private key algorithm %q", certificate.Spec.KeyAlgorithm)
}
// TODO: validate private key KeySize
// check the provided certificate is valid
expectedCN := pki.CommonNameForCertificate(certificate)
expectedOrganization := pki.OrganizationForCertificate(certificate)
expectedDNSNames := pki.DNSNamesForCertificate(certificate)
certBytes, ok := secret.Data[v1.TLSCertKey]
if !ok {
glog.Infof("No certificate data found for Certificate %q (secret %q)", name, certificate.Spec.SecretName)
return false, nil
}
cert, err := pki.DecodeX509CertificateBytes(certBytes)
if err != nil {
return false, err
}
if expectedCN != cert.Subject.CommonName || !util.EqualUnsorted(cert.DNSNames, expectedDNSNames) || !(len(cert.Subject.Organization) == 0 || util.EqualUnsorted(cert.Subject.Organization, expectedOrganization)) {
glog.Infof("Expected certificate valid for CN %q, O %v, dnsNames %v but got a certificate valid for CN %q, O %v, dnsNames %v", expectedCN, expectedOrganization, expectedDNSNames, cert.Subject.CommonName, cert.Subject.Organization, cert.DNSNames)
return false, nil
}
label, ok := secret.Labels[v1alpha1.CertificateNameKey]
if !ok {
return false, fmt.Errorf("Expected secret to have certificate-name label, but had none")
}
if label != certificate.Name {
return false, fmt.Errorf("Expected secret to have certificate-name label with a value of %q, but got %q", certificate.Name, label)
}
return true, nil
},
)
}
// WaitForCertificateToExist waits for the named certificate to exist
func WaitForCertificateToExist(client clientset.CertificateInterface, name string, timeout time.Duration) error {
return wait.PollImmediate(500*time.Millisecond, timeout,
func() (bool, error) {
glog.V(5).Infof("Waiting for Certificate %v to exist", name)
_, err := client.Get(name, metav1.GetOptions{})
if errors.IsNotFound(err) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("error getting Certificate %v: %v", name, err)
}
return true, nil
},
)
}
// WaitForCRDToNotExist waits for the CRD with the given name to no
// longer exist.
func WaitForCRDToNotExist(client apiextcs.CustomResourceDefinitionInterface, name string) error {
return wait.PollImmediate(500*time.Millisecond, wait.ForeverTestTimeout,
func() (bool, error) {
glog.V(5).Infof("Waiting for CRD %v to not exist", name)
_, err := client.Get(name, metav1.GetOptions{})
if nil == err {
return false, nil
}
if errors.IsNotFound(err) {
return true, nil
}
return false, nil
},
)
}
func NewCertManagerCAClusterIssuer(name, secretName string) *v1alpha1.ClusterIssuer {
return &v1alpha1.ClusterIssuer{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.IssuerSpec{
IssuerConfig: v1alpha1.IssuerConfig{
CA: &v1alpha1.CAIssuer{
SecretName: secretName,
},
},
},
}
}
func NewCertManagerBasicCertificate(name, secretName, issuerName string, issuerKind string) *v1alpha1.Certificate {
return &v1alpha1.Certificate{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.CertificateSpec{
CommonName: "test.domain.com",
Organization: []string{"test-org"},
SecretName: secretName,
IssuerRef: v1alpha1.ObjectReference{
Name: issuerName,
Kind: issuerKind,
},
},
}
}
func NewCertManagerACMECertificate(name, secretName, issuerName string, issuerKind string, ingressClass string, cn string, dnsNames ...string) *v1alpha1.Certificate {
return &v1alpha1.Certificate{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.CertificateSpec{
CommonName: cn,
DNSNames: dnsNames,
SecretName: secretName,
IssuerRef: v1alpha1.ObjectReference{
Name: issuerName,
Kind: issuerKind,
},
ACME: &v1alpha1.ACMECertificateConfig{
Config: []v1alpha1.DomainSolverConfig{
{
Domains: append(dnsNames, cn),
SolverConfig: v1alpha1.SolverConfig{
HTTP01: &v1alpha1.HTTP01SolverConfig{
IngressClass: &ingressClass,
},
},
},
},
},
},
}
}
func NewCertManagerVaultCertificate(name, secretName, issuerName string, issuerKind string) *v1alpha1.Certificate {
return &v1alpha1.Certificate{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.CertificateSpec{
CommonName: "test.domain.com",
SecretName: secretName,
IssuerRef: v1alpha1.ObjectReference{
Name: issuerName,
Kind: issuerKind,
},
},
}
}
func NewIngress(name, secretName string, annotations map[string]string, dnsNames ...string) *extv1beta1.Ingress {
return &extv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Annotations: annotations,
},
Spec: extv1beta1.IngressSpec{
TLS: []extv1beta1.IngressTLS{
{
Hosts: dnsNames,
SecretName: secretName,
},
},
Rules: []extv1beta1.IngressRule{
{
Host: dnsNames[0],
IngressRuleValue: extv1beta1.IngressRuleValue{
HTTP: &extv1beta1.HTTPIngressRuleValue{
Paths: []extv1beta1.HTTPIngressPath{
{
Path: "/",
Backend: extv1beta1.IngressBackend{
ServiceName: "dummy-service",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
},
},
}
}
func NewCertManagerACMEIssuer(name, acmeURL, acmeEmail, acmePrivateKey string) *v1alpha1.Issuer {
return &v1alpha1.Issuer{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.IssuerSpec{
IssuerConfig: v1alpha1.IssuerConfig{
ACME: &v1alpha1.ACMEIssuer{
Email: acmeEmail,
Server: acmeURL,
SkipTLSVerify: true,
PrivateKey: v1alpha1.SecretKeySelector{
LocalObjectReference: v1alpha1.LocalObjectReference{
Name: acmePrivateKey,
},
},
HTTP01: &v1alpha1.ACMEIssuerHTTP01Config{},
},
},
},
}
}
func NewCertManagerCAIssuer(name, secretName string) *v1alpha1.Issuer {
return &v1alpha1.Issuer{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.IssuerSpec{
IssuerConfig: v1alpha1.IssuerConfig{
CA: &v1alpha1.CAIssuer{
SecretName: secretName,
},
},
},
}
}
func NewCertManagerSelfSignedIssuer(name string) *v1alpha1.Issuer {
return &v1alpha1.Issuer{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.IssuerSpec{
IssuerConfig: v1alpha1.IssuerConfig{
SelfSigned: &v1alpha1.SelfSignedIssuer{},
},
},
}
}
func NewCertManagerVaultIssuerToken(name, vaultURL, vaultPath, vaultSecretToken, authPath string, caBundle []byte) *v1alpha1.Issuer {
return &v1alpha1.Issuer{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.IssuerSpec{
IssuerConfig: v1alpha1.IssuerConfig{
Vault: &v1alpha1.VaultIssuer{
Server: vaultURL,
Path: vaultPath,
CABundle: caBundle,
Auth: v1alpha1.VaultAuth{
TokenSecretRef: v1alpha1.SecretKeySelector{
Key: "secretkey",
LocalObjectReference: v1alpha1.LocalObjectReference{
Name: vaultSecretToken,
},
},
},
},
},
},
}
}
func NewCertManagerVaultIssuerAppRole(name, vaultURL, vaultPath, roleId, vaultSecretAppRole string, authPath string, caBundle []byte) *v1alpha1.Issuer {
return &v1alpha1.Issuer{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha1.IssuerSpec{
IssuerConfig: v1alpha1.IssuerConfig{
Vault: &v1alpha1.VaultIssuer{
Server: vaultURL,
Path: vaultPath,
CABundle: caBundle,
Auth: v1alpha1.VaultAuth{
AppRole: v1alpha1.VaultAppRole{
Path: authPath,
RoleId: roleId,
SecretRef: v1alpha1.SecretKeySelector{
Key: "secretkey",
LocalObjectReference: v1alpha1.LocalObjectReference{
Name: vaultSecretAppRole,
},
},
},
},
},
},
},
}
}
func NewSigningKeypairSecret(name string) *v1.Secret {
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
StringData: map[string]string{
v1.TLSCertKey: `-----BEGIN CERTIFICATE-----
MIID4DCCAsigAwIBAgIJAJzTROInmDkQMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNV
BAYTAlVLMQswCQYDVQQIEwJOQTEVMBMGA1UEChMMY2VydC1tYW5hZ2VyMSAwHgYD
VQQDExdjZXJ0LW1hbmFnZXIgdGVzdGluZyBDQTAeFw0xNzA5MTAxODMzNDNaFw0y
NzA5MDgxODMzNDNaMFMxCzAJBgNVBAYTAlVLMQswCQYDVQQIEwJOQTEVMBMGA1UE
ChMMY2VydC1tYW5hZ2VyMSAwHgYDVQQDExdjZXJ0LW1hbmFnZXIgdGVzdGluZyBD
QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM+Q2AO4hARav0qwjk7I
4mEh5R201HS8s7HpaLOXBNvvh7qJ9yJz6jLqYg6EvP0K/bK56Cp2oe2igd7GOxpV
3YPOc3CG0CCqHMprEcvxj2xBKX00Rtcn4oVLhDPhAb0BV/R7NFLeWxzh+ggvPI1X
m1qLaWYqYZEJ5bBsYXD3tPdS4GGINRz8Zvih46f0Z2wVkCGoTpsbX8HO74sa2Day
UjzAsWGlO5bZGiMSHjDEnf9yek2TcjEyVoohoOLaQg/ng21T5RWzeZKTl1cznwuG
Vr9tZfHFqxQ5qeaId+1ICtxNvkEjbTnZl6Wy9Cthn0dxwOeS5TqMJ7SFNXy1gp4j
f/MCAwEAAaOBtjCBszAdBgNVHQ4EFgQUBtrjvWfbkLA0iX6sKVRhKUo864kwgYMG
A1UdIwR8MHqAFAba471n25CwNIl+rClUYSlKPOuJoVekVTBTMQswCQYDVQQGEwJV
SzELMAkGA1UECBMCTkExFTATBgNVBAoTDGNlcnQtbWFuYWdlcjEgMB4GA1UEAxMX
Y2VydC1tYW5hZ2VyIHRlc3RpbmcgQ0GCCQCc00TiJ5g5EDAMBgNVHRMEBTADAQH/
MA0GCSqGSIb3DQEBCwUAA4IBAQCR+jXhup5tCKwhAf8xgvp589BczQOjmotuZGEL
Dcint2y263ChEdsoLhyJfvFCAZfTSm+UT95Hl+ZKVuoVEcAS7udaFUFpC/gIYVOi
H4/uvJps4SpVCB7+T/orcTjZ2ewT23mQAQg+B+iwX9VCof+fadkYOg1XD9/eaj6E
9McXID3iuCXg02RmEOwVMrTggHPwHrOGAilSaZc58cJZHmMYlT5rGrJcWS/AyXnH
VOodKC004yjh7w9aSbCCbAL0tDEnhm4Jrb8cxt7pDWbdEVUeuk9LZRQtluYBnmJU
kQ7ALfUfUh/RUpCV4uI6sEI3NDX2YqQbOtsBD/hNaL1F85FA
-----END CERTIFICATE-----`,
v1.TLSPrivateKeyKey: `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAz5DYA7iEBFq/SrCOTsjiYSHlHbTUdLyzselos5cE2++Huon3
InPqMupiDoS8/Qr9srnoKnah7aKB3sY7GlXdg85zcIbQIKocymsRy/GPbEEpfTRG
1yfihUuEM+EBvQFX9Hs0Ut5bHOH6CC88jVebWotpZiphkQnlsGxhcPe091LgYYg1
HPxm+KHjp/RnbBWQIahOmxtfwc7vixrYNrJSPMCxYaU7ltkaIxIeMMSd/3J6TZNy
MTJWiiGg4tpCD+eDbVPlFbN5kpOXVzOfC4ZWv21l8cWrFDmp5oh37UgK3E2+QSNt
OdmXpbL0K2GfR3HA55LlOowntIU1fLWCniN/8wIDAQABAoIBAQCYvGvIKSG0FpbG
vi6pmLbEZO20s1jW4fiUxT2PUWR49sR4pocdahB/EOvA5TowNcNDnftSK+Ox+q/4
HwRkt6R+Fg/qULmcH7F53dnFqeYw8a42/J3YOvg7v7rzdfISg4eWVobFJ+wBz+Nt
3FyBYWLm+MlBLZSH5rGG5em59/zJNHWIhH+oQPfCxAkYEvd8tXOTUzjhqvEfjaJy
FZghnT9xto4MwDdNCPbtzdNjTMhiv0AHkcZGGtRJfkehXX2qhXOQ2UzzO9XrMZnv
5KgYf+bXKJsyS3SPl6TTl7vg2gKBciRvsdFhMy5I5GyIADrEDJnNNmXQRtiaFLfd
k/aqfPT5AoGBAPquMouZUbVS/Qh+qbls7G4zAuznfCiqdctcKmUGPRP4sTTjWdUp
fjI+UTt1e8hncmr4RY7Oa9kUV/kDwzS5spUZZ+u0PczS3XKxOwNOleoH00dfc9vt
cxctHdPdDTndRi8Z4k3m931jIX7jB/Pyx8qeNYB3pj0k3ThktwMbAVLnAoGBANP4
beI5zpbvtAdExJcuxx2mRDGF0lIdKC0bvQaeqM3Lwqnmc0Fz1dbP7KXDa+SdJWPd
res+NHPZoEPeEJuDTSngXOLNECZe4Ja9frn1TeY858vMJBwIkyc8zu+sgXxjQUM+
TWUlTUhtXyybkRnxAEny4OT2TTgmXITJaKOmV1UVAoGAHaXSlo4YitB42rNYUXTf
dZ0U4H30Qj7+1YFeBjq5qI4GL1IgQsS4hyq1osmfTTFm593bJCunt7HfQbU/NhIs
W9P4ZXkYwgvCYxkw+JAnzNkGFO/mHQG1Ve1hFLiVIt3XuiRejoYdiTfbM02YmDKD
jKQvgbUk9SBSBaRrvLNJ8csCgYAYnrZEnGo+ZcEHRxl+ZdSCwRkSl3SCTRiphJtD
9ZGttYj6quWgKJAhzyyxZC1X9FivbMQSmrsE6bYPq+9J4MpJnuGrBh5mFocHeyMI
/lD5+QEDTsay6twMpqdydxrjE7Q01zuuD9MWIn33dGo6FR/vduJgNatqZipA0hPx
ThS+sQKBgQDh0+cVo1mfYiCkp3IQPB8QYiJ/g2/UBk6pH8ZZDZ+A5td6NveiWO1y
wTEUWkX2qyz9SLxWDGOhdKqxNrLCUSYSOV/5/JQEtBm6K50ArFtrY40JP/T/5KvM
tSK2ayFX1wQ3PuEmewAogy/20tWo80cr556AXA62Utl2PzLK30Db8w==
-----END RSA PRIVATE KEY-----`,
},
}
}