summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRemigiusz Janeczek <remigiusz.janeczek@nokia.com>2020-10-22 09:18:12 +0200
committerRemigiusz Janeczek <remigiusz.janeczek@nokia.com>2020-10-22 16:00:36 +0000
commitee23e5f54f96807b1f1fff0b45238a247d3dd8e0 (patch)
treeec390b860e0c10810bd778a1b68dbfc8ab12c64a
parentaa23960c5d444dea307e0934b446f12ab0256689 (diff)
[OOM-K8S-CERT-EXTERNAL-PROVIDER] Add client for CertService API
Issue-ID: OOM-2559 Signed-off-by: Remigiusz Janeczek <remigiusz.janeczek@nokia.com> Change-Id: I3bf6c36b9eec7a661202b18eb7765e332ccfbc07
-rw-r--r--certServiceK8sExternalProvider/deploy/_certificate_example_.yaml18
-rw-r--r--certServiceK8sExternalProvider/src/certserviceclient/cert_service_client.go73
-rw-r--r--certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_factory.go74
-rw-r--r--certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_factory_test.go95
-rw-r--r--certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_test.go101
-rw-r--r--certServiceK8sExternalProvider/src/cmpv2controller/certificate_request_controller.go29
-rw-r--r--certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner.go39
-rw-r--r--certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory.go15
-rw-r--r--certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory_test.go29
-rw-r--r--certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_test.go54
-rw-r--r--certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/expected_signed.pem (renamed from certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/expected_signed.pem)0
-rw-r--r--certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/expected_trusted.pem (renamed from certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/expected_trusted.pem)0
-rw-r--r--certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/test_certificate.pem (renamed from certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/test_certificate.pem)0
-rw-r--r--certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/test_certificate_request.pem (renamed from certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/test_certificate_request.pem)0
-rw-r--r--certServiceK8sExternalProvider/src/testdata/constants.go12
15 files changed, 471 insertions, 68 deletions
diff --git a/certServiceK8sExternalProvider/deploy/_certificate_example_.yaml b/certServiceK8sExternalProvider/deploy/_certificate_example_.yaml
index 60665d7f..e19e6351 100644
--- a/certServiceK8sExternalProvider/deploy/_certificate_example_.yaml
+++ b/certServiceK8sExternalProvider/deploy/_certificate_example_.yaml
@@ -32,17 +32,21 @@ spec:
secretName: _sample_secret_name_
# Common Name
commonName: certissuer.onap.org
+ subject:
+ organizations:
+ - Linux-Foundation
+ countries:
+ - US
+ localities:
+ - San-Francisco
+ provinces:
+ - California
+ organizationalUnits:
+ - ONAP
# DNS SAN
dnsNames:
- localhost
- certissuer.onap.org
- # IP Address SAN
- ipAddresses:
- - "127.0.0.1"
- # Duration of the certificate
- duration: 24h
- # Renew 8 hours before the certificate expiration
- renewBefore: 8h
# The reference to the CMPv2 issuer
issuerRef:
group: certmanager.onap.org
diff --git a/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client.go b/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client.go
new file mode 100644
index 00000000..870a3eda
--- /dev/null
+++ b/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client.go
@@ -0,0 +1,73 @@
+/*
+ * ============LICENSE_START=======================================================
+ * oom-certservice-k8s-external-provider
+ * ================================================================================
+ * Copyright (C) 2020 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package certserviceclient
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "net/http"
+)
+
+const (
+ CsrHeaderName = "CSR"
+ PkHeaderName = "PK"
+)
+
+type CertServiceClient interface {
+ GetCertificates(csr []byte, key []byte) (*CertificatesResponse, error)
+}
+
+type CertServiceClientImpl struct {
+ certificationUrl string
+ httpClient HTTPClient
+}
+
+type HTTPClient interface {
+ Do(req *http.Request) (*http.Response, error)
+}
+
+type CertificatesResponse struct {
+ CertificateChain []string `json:"certificateChain"`
+ TrustedCertificates []string `json:"trustedCertificates"`
+}
+
+func (client *CertServiceClientImpl) GetCertificates(csr []byte, key []byte) (*CertificatesResponse, error) {
+
+ request, err := http.NewRequest("GET", client.certificationUrl, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ request.Header.Add(CsrHeaderName, base64.StdEncoding.EncodeToString(csr))
+ request.Header.Add(PkHeaderName, base64.StdEncoding.EncodeToString(key))
+ response, err := client.httpClient.Do(request)
+ if err != nil {
+ return nil, err
+ }
+
+ var certificatesResponse CertificatesResponse
+ err = json.NewDecoder(response.Body).Decode(&certificatesResponse)
+ if err != nil {
+ return nil, err
+ }
+
+ return &certificatesResponse, err
+}
diff --git a/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_factory.go b/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_factory.go
new file mode 100644
index 00000000..198f2294
--- /dev/null
+++ b/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_factory.go
@@ -0,0 +1,74 @@
+/*
+ * ============LICENSE_START=======================================================
+ * oom-certservice-k8s-external-provider
+ * ================================================================================
+ * Copyright (C) 2020 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package certserviceclient
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "net/http"
+ "net/url"
+ "path"
+)
+
+func CreateCertServiceClient(baseUrl string, caName string, keyPemBase64 []byte, certPemBase64 []byte, cacertPemBase64 []byte) (*CertServiceClientImpl, error) {
+ cert, err := tls.X509KeyPair(certPemBase64, keyPemBase64)
+ if err != nil {
+ return nil, err
+ }
+ x509.NewCertPool()
+ caCertPool := x509.NewCertPool()
+ ok := caCertPool.AppendCertsFromPEM(cacertPemBase64)
+ if !ok {
+ return nil, fmt.Errorf("couldn't certs from cacert")
+ }
+ httpClient := &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: caCertPool,
+ Certificates: []tls.Certificate{cert},
+ },
+ },
+ }
+ certificationUrl, err := parseUrl(baseUrl, caName)
+ if err != nil {
+ return nil, err
+ }
+ client := CertServiceClientImpl{
+ certificationUrl: certificationUrl.String(),
+ httpClient: httpClient,
+ }
+
+ return &client, nil
+}
+
+func parseUrl(baseUrl string, caName string) (*url.URL, error) {
+ parsedUrl, err := url.Parse(baseUrl)
+ if err != nil {
+ return nil, err
+ }
+ if caName == "" {
+ return nil, fmt.Errorf("caName cannot be empty")
+ }
+
+ parsedUrl.Path = path.Join(parsedUrl.Path, caName)
+ return parsedUrl, nil
+}
diff --git a/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_factory_test.go b/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_factory_test.go
new file mode 100644
index 00000000..50a6d796
--- /dev/null
+++ b/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_factory_test.go
@@ -0,0 +1,95 @@
+/*
+ * ============LICENSE_START=======================================================
+ * oom-certservice-k8s-external-provider
+ * ================================================================================
+ * Copyright (C) 2020 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package certserviceclient
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "onap.org/oom-certservice/k8s-external-provider/src/testdata"
+)
+
+const (
+ validUrl = "https://oom-cert-service:8443/v1/certificate/"
+ validUrl2 = "https://oom-cert-service:8443/v1/certificate"
+ invalidUrl = "https://oom-cert service:8443/v1/certificate"
+ caName = "RA"
+ expectedCertificationUrl = "https://oom-cert-service:8443/v1/certificate/RA"
+)
+
+func Test_shouldCreateCertServiceClient(t *testing.T) {
+ shouldCreateCertServiceClientWithExpectedUrl(t, expectedCertificationUrl, validUrl)
+ shouldCreateCertServiceClientWithExpectedUrl(t, expectedCertificationUrl, validUrl2)
+}
+
+func shouldCreateCertServiceClientWithExpectedUrl(t *testing.T, expectedCertificationUrl string, baseUrl string) {
+ client, err := CreateCertServiceClient(baseUrl, caName, testdata.KeyBytes, testdata.CertBytes, testdata.CacertBytes)
+
+ assert.NotNil(t, client)
+ assert.Nil(t, err)
+ assert.Equal(t, expectedCertificationUrl, client.certificationUrl)
+}
+
+func Test_shouldReturnError_whenUrlInvalid(t *testing.T) {
+ client, err := CreateCertServiceClient(invalidUrl, caName, testdata.KeyBytes, testdata.CertBytes, testdata.CacertBytes)
+
+ assert.Nil(t, client)
+ assert.Error(t, err)
+}
+
+func Test_shouldReturnError_whenCanameEmpty(t *testing.T) {
+ client, err := CreateCertServiceClient(validUrl, "", testdata.KeyBytes, testdata.CertBytes, testdata.CacertBytes)
+
+ assert.Nil(t, client)
+ assert.Error(t, err)
+}
+
+func Test_shouldReturnError_whenKeyNotMatchingCert(t *testing.T) {
+ client, err := CreateCertServiceClient(validUrl, caName, testdata.NotMatchingKeyBytes, testdata.CertBytes, testdata.CacertBytes)
+
+ assert.Nil(t, client)
+ assert.Error(t, err)
+}
+
+func Test_shouldReturnError_whenKeyInvalid(t *testing.T) {
+ //Cert used as key
+ client, err := CreateCertServiceClient(validUrl, caName, testdata.CertBytes, testdata.CertBytes, testdata.CacertBytes)
+
+ assert.Nil(t, client)
+ assert.Error(t, err)
+}
+
+func Test_shouldReturnError_whenCertInvalid(t *testing.T) {
+ //Cacert used as cert
+ client, err := CreateCertServiceClient(validUrl, caName, testdata.KeyBytes, testdata.CacertBytes, testdata.CacertBytes)
+
+ assert.Nil(t, client)
+ assert.Error(t, err)
+}
+
+func Test_shouldReturnError_whenCacertInvalid(t *testing.T) {
+ //Key used as cacert
+ client, err := CreateCertServiceClient(validUrl, caName, testdata.KeyBytes, testdata.CertBytes, testdata.KeyBytes)
+
+ assert.Nil(t, client)
+ assert.Error(t, err)
+}
diff --git a/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_test.go b/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_test.go
new file mode 100644
index 00000000..1e15d43e
--- /dev/null
+++ b/certServiceK8sExternalProvider/src/certserviceclient/cert_service_client_test.go
@@ -0,0 +1,101 @@
+/*
+ * ============LICENSE_START=======================================================
+ * oom-certservice-k8s-external-provider
+ * ================================================================================
+ * Copyright (C) 2020 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package certserviceclient
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "onap.org/oom-certservice/k8s-external-provider/src/testdata"
+)
+
+const (
+ certificationUrl = "https://oom-cert-service:8443/v1/certificate/RA"
+)
+
+
+func Test_shouldParseCertificateResponseCorrectly(t *testing.T) {
+ responseJson := `{"certificateChain": ["cert-0", "cert-1"], "trustedCertificates": ["trusted-cert-0", "trusted-cert-1"]}`
+ responseJsonReader := ioutil.NopCloser(bytes.NewReader([]byte(responseJson)))
+ client := CertServiceClientImpl{
+ certificationUrl: certificationUrl,
+ httpClient: &httpClientMock{
+ DoFunc: func(req *http.Request) (response *http.Response, e error) {
+ mockedResponse := &http.Response{
+ Body: responseJsonReader,
+ }
+ return mockedResponse, nil
+ },
+ },
+ }
+ response, _ := client.GetCertificates(testdata.CsrBytes, testdata.PkBytes)
+ assert.ElementsMatch(t, []string{"cert-0", "cert-1"}, response.CertificateChain)
+ assert.ElementsMatch(t, []string{"trusted-cert-0", "trusted-cert-1"}, response.TrustedCertificates)
+}
+
+func Test_shouldReturnError_whenResponseIsNotJson(t *testing.T) {
+ responseJson := `not a json`
+ responseJsonReader := ioutil.NopCloser(bytes.NewReader([]byte(responseJson)))
+ client := CertServiceClientImpl{
+ certificationUrl: certificationUrl,
+ httpClient: &httpClientMock{
+ DoFunc: func(req *http.Request) (response *http.Response, e error) {
+ mockedResponse := &http.Response{
+ Body: responseJsonReader,
+ }
+ return mockedResponse, nil
+ },
+ },
+ }
+ response, err := client.GetCertificates(testdata.CsrBytes, testdata.PkBytes)
+
+ assert.Nil(t, response)
+ assert.Error(t, err)
+}
+
+func Test_shouldReturnError_whenHttpClientReturnsError(t *testing.T) {
+ client := CertServiceClientImpl{
+ certificationUrl: certificationUrl,
+ httpClient: &httpClientMock{
+ DoFunc: func(req *http.Request) (response *http.Response, err error) {
+ return nil, fmt.Errorf("mock error")
+ },
+ },
+ }
+ response, err := client.GetCertificates(testdata.CsrBytes, testdata.PkBytes)
+
+ assert.Nil(t, response)
+ assert.Error(t, err)
+}
+
+
+type httpClientMock struct {
+ DoFunc func(*http.Request) (*http.Response, error)
+}
+
+func (client httpClientMock) Do(req *http.Request) (*http.Response, error) {
+ return client.DoFunc(req)
+}
diff --git a/certServiceK8sExternalProvider/src/cmpv2controller/certificate_request_controller.go b/certServiceK8sExternalProvider/src/cmpv2controller/certificate_request_controller.go
index 54b4b103..d526bbc8 100644
--- a/certServiceK8sExternalProvider/src/cmpv2controller/certificate_request_controller.go
+++ b/certServiceK8sExternalProvider/src/cmpv2controller/certificate_request_controller.go
@@ -44,6 +44,11 @@ import (
provisioners "onap.org/oom-certservice/k8s-external-provider/src/cmpv2provisioner"
)
+const (
+ privateKeySecretNameAnnotation = "cert-manager.io/private-key-secret-name"
+ privateKeySecretKey = "tls.key"
+)
+
// CertificateRequestController reconciles a CMPv2Issuer object.
type CertificateRequestController struct {
client.Client
@@ -104,14 +109,27 @@ func (controller *CertificateRequestController) Reconcile(k8sRequest ctrl.Reques
return ctrl.Result{}, err
}
- // 7. Sign CertificateRequest
- signedPEM, trustedCAs, err := provisioner.Sign(ctx, certificateRequest)
+ // 7. Get private key matching CertificateRequest
+ privateKeySecretName := certificateRequest.ObjectMeta.Annotations[privateKeySecretNameAnnotation]
+ privateKeySecretNamespaceName := types.NamespacedName{
+ Namespace: k8sRequest.Namespace,
+ Name: privateKeySecretName,
+ }
+ var privateKeySecret core.Secret
+ if err := controller.Client.Get(ctx, privateKeySecretNamespaceName, &privateKeySecret); err != nil {
+ controller.handleErrorGettingPrivateKey(ctx, log, err, certificateRequest, privateKeySecretNamespaceName)
+ return ctrl.Result{}, err
+ }
+ privateKeyBytes := privateKeySecret.Data[privateKeySecretKey]
+
+ // 8. Sign CertificateRequest
+ signedPEM, trustedCAs, err := provisioner.Sign(ctx, certificateRequest, privateKeyBytes)
if err != nil {
controller.handleErrorFailedToSignCertificate(ctx, log, err, certificateRequest)
return ctrl.Result{}, err
}
- // 8. Store signed certificates in CertificateRequest
+ // 9. Store signed certificates in CertificateRequest
certificateRequest.Status.Certificate = signedPEM
certificateRequest.Status.CA = trustedCAs
if err := controller.updateCertificateRequestWithSignedCerficates(ctx, certificateRequest); err != nil {
@@ -188,6 +206,11 @@ func (controller *CertificateRequestController) handleErrorGettingCMPv2Issuer(ct
_ = controller.setStatus(ctx, certificateRequest, cmmeta.ConditionFalse, cmapi.CertificateRequestReasonPending, "Failed to retrieve CMPv2Issuer resource %s: %v", issuerNamespaceName, err)
}
+func (controller *CertificateRequestController) handleErrorGettingPrivateKey(ctx context.Context, log logr.Logger, err error, certificateRequest *cmapi.CertificateRequest, pkSecretNamespacedName types.NamespacedName) {
+ log.Error(err, "Failed to retrieve private key secret for certificate request", "namespace", pkSecretNamespacedName.Namespace, "name", pkSecretNamespacedName.Name)
+ _ = controller.setStatus(ctx, certificateRequest, cmmeta.ConditionFalse, cmapi.CertificateRequestReasonPending, "Failed to retrieve private key secret: %v", err)
+}
+
func (controller *CertificateRequestController) handleErrorFailedToSignCertificate(ctx context.Context, log logr.Logger, err error, certificateRequest *cmapi.CertificateRequest) {
log.Error(err, "Failed to sign certificate request")
_ = controller.setStatus(ctx, certificateRequest, cmmeta.ConditionFalse, cmapi.CertificateRequestReasonFailed, "Failed to sign certificate request: %v", err)
diff --git a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner.go b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner.go
index e48b527d..67d719cc 100644
--- a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner.go
+++ b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner.go
@@ -38,33 +38,29 @@ import (
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
+ "onap.org/oom-certservice/k8s-external-provider/src/certserviceclient"
"onap.org/oom-certservice/k8s-external-provider/src/cmpv2api"
)
var collection = new(sync.Map)
type CertServiceCA struct {
- name string
- url string
- caName string
- key []byte
- cert []byte
- cacert []byte
+ name string
+ url string
+ caName string
+ certServiceClient certserviceclient.CertServiceClient
}
-func New(cmpv2Issuer *cmpv2api.CMPv2Issuer, key []byte, cert []byte, cacert []byte) (*CertServiceCA, error) {
+func New(cmpv2Issuer *cmpv2api.CMPv2Issuer, certServiceClient certserviceclient.CertServiceClient) (*CertServiceCA, error) {
ca := CertServiceCA{}
ca.name = cmpv2Issuer.Name
ca.url = cmpv2Issuer.Spec.URL
ca.caName = cmpv2Issuer.Spec.CaName
- ca.key = key
- ca.cert = cert
- ca.cacert = cacert
+ ca.certServiceClient = certServiceClient
log := ctrl.Log.WithName("cmpv2-provisioner")
- log.Info("Configuring CA: ", "name", ca.name, "url", ca.url, "caName", ca.caName, "key", ca.key,
- "cert", ca.cert, "cacert", ca.cacert)
+ log.Info("Configuring CA: ", "name", ca.name, "url", ca.url, "caName", ca.caName)
return &ca, nil
}
@@ -82,22 +78,27 @@ func Store(namespacedName types.NamespacedName, provisioner *CertServiceCA) {
collection.Store(namespacedName, provisioner)
}
-func (ca *CertServiceCA) Sign(ctx context.Context, certificateRequest *certmanager.CertificateRequest) ([]byte, []byte, error) {
+func (ca *CertServiceCA) Sign(ctx context.Context, certificateRequest *certmanager.CertificateRequest, privateKeyBytes []byte) ([]byte, []byte, error) {
log := ctrl.Log.WithName("certservice-provisioner")
log.Info("Signing certificate: ", "cert-name", certificateRequest.Name)
- key, _ := base64.RawStdEncoding.DecodeString(string(ca.key))
- log.Info("CA: ", "name", ca.name, "url", ca.url, "key", key)
+ log.Info("CA: ", "name", ca.name, "url", ca.url)
- crPEM := certificateRequest.Spec.Request
- csrBase64 := crPEM
- log.Info("Csr PEM: ", "bytes", csrBase64)
+ csrBytes := certificateRequest.Spec.Request
+ log.Info("Csr PEM: ", "bytes", csrBytes)
- csr, err := decodeCSR(crPEM)
+ csr, err := decodeCSR(csrBytes)
if err != nil {
return nil, nil, err
}
+ response, err := ca.certServiceClient.GetCertificates(csrBytes, privateKeyBytes)
+ if err != nil {
+ return nil, nil, err
+ }
+ log.Info("Certificate Chain", "cert-chain", response.CertificateChain)
+ log.Info("Trusted Certificates", "trust-certs", response.TrustedCertificates)
+
cert := x509.Certificate{}
cert.Raw = csr.Raw
diff --git a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory.go b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory.go
index 4a3898e7..125c1bc6 100644
--- a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory.go
+++ b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory.go
@@ -25,24 +25,31 @@ import (
v1 "k8s.io/api/core/v1"
+ "onap.org/oom-certservice/k8s-external-provider/src/certserviceclient"
"onap.org/oom-certservice/k8s-external-provider/src/cmpv2api"
)
func CreateProvisioner(issuer *cmpv2api.CMPv2Issuer, secret v1.Secret) (*CertServiceCA, error) {
secretKeys := issuer.Spec.CertSecretRef
- key, err := readValueFromSecret(secret, secretKeys.KeyRef)
+ keyBase64, err := readValueFromSecret(secret, secretKeys.KeyRef)
if err != nil {
return nil, err
}
- cert, err := readValueFromSecret(secret, secretKeys.CertRef)
+ certBase64, err := readValueFromSecret(secret, secretKeys.CertRef)
if err != nil {
return nil, err
}
- cacert, err := readValueFromSecret(secret, secretKeys.CacertRef)
+ cacertBase64, err := readValueFromSecret(secret, secretKeys.CacertRef)
if err != nil {
return nil, err
}
- return New(issuer, key, cert, cacert)
+
+ certServiceClient, err := certserviceclient.CreateCertServiceClient(issuer.Spec.URL, issuer.Spec.CaName, keyBase64, certBase64, cacertBase64)
+ if err != nil {
+ return nil, err
+ }
+
+ return New(issuer, certServiceClient)
}
func readValueFromSecret(secret v1.Secret, secretKey string) ([]byte, error) {
diff --git a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory_test.go b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory_test.go
index 6ef33098..1e215d3f 100644
--- a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory_test.go
+++ b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_factory_test.go
@@ -21,6 +21,7 @@
package cmpv2provisioner
import (
+ "encoding/base64"
"fmt"
"testing"
@@ -28,6 +29,7 @@ import (
v1 "k8s.io/api/core/v1"
"onap.org/oom-certservice/k8s-external-provider/src/cmpv2api"
+ "onap.org/oom-certservice/k8s-external-provider/src/testdata"
)
const (
@@ -39,12 +41,6 @@ const (
cacertSecretKey = "cacert.pem"
)
-var (
- keySecretValue = []byte("keyData")
- certSecretValue = []byte("certData")
- cacertSecretValue = []byte("cacertData")
-)
-
func Test_shouldCreateProvisioner(t *testing.T) {
issuer, secret := getValidIssuerAndSecret()
@@ -53,9 +49,6 @@ func Test_shouldCreateProvisioner(t *testing.T) {
assert.NotNil(t, provisioner)
assert.Equal(t, url, provisioner.url)
assert.Equal(t, caName, provisioner.caName)
- assert.Equal(t, keySecretValue, provisioner.key)
- assert.Equal(t, certSecretValue, provisioner.cert)
- assert.Equal(t, cacertSecretValue, provisioner.cacert)
}
func Test_shouldReturnError_whenSecretMissingKeyRef(t *testing.T) {
@@ -94,6 +87,18 @@ func Test_shouldReturnError_whenSecretMissingCacertRef(t *testing.T) {
}
}
+
+func Test_shouldReturnError_whenCreationOfCertServiceClientReturnsError(t *testing.T) {
+ issuer, secret := getValidIssuerAndSecret()
+ invalidKeySecretValue, _ := base64.StdEncoding.DecodeString("")
+ secret.Data[keySecretKey] = invalidKeySecretValue
+
+ provisioner, err := CreateProvisioner(&issuer, secret)
+
+ assert.Nil(t, provisioner)
+ assert.Error(t, err)
+}
+
func getValidIssuerAndSecret() (cmpv2api.CMPv2Issuer, v1.Secret) {
issuer := cmpv2api.CMPv2Issuer{
Spec: cmpv2api.CMPv2IssuerSpec{
@@ -110,9 +115,9 @@ func getValidIssuerAndSecret() (cmpv2api.CMPv2Issuer, v1.Secret) {
secret := v1.Secret{
Data: map[string][]byte{
- keySecretKey: keySecretValue,
- certSecretKey: certSecretValue,
- cacertSecretKey: cacertSecretValue,
+ keySecretKey: testdata.KeyBytes,
+ certSecretKey: testdata.CertBytes,
+ cacertSecretKey: testdata.CacertBytes,
},
}
secret.Name = secretName
diff --git a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_test.go b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_test.go
index f3ab5cb0..39e399b8 100644
--- a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_test.go
+++ b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_test.go
@@ -33,31 +33,26 @@ import (
apimach "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
+ "onap.org/oom-certservice/k8s-external-provider/src/certserviceclient"
"onap.org/oom-certservice/k8s-external-provider/src/cmpv2api"
)
const ISSUER_NAME = "cmpv2-issuer"
const ISSUER_URL = "issuer/url"
-const KEY = "onapwro-key"
-const CERT = "onapwro-cert"
-const CACERT = "onapwro-cacert"
const ISSUER_NAMESPACE = "onap"
func Test_shouldCreateCorrectCertServiceCA(t *testing.T) {
- issuer, key, cert, cacert := createIssuerAndCerts(ISSUER_NAME, ISSUER_URL, KEY, CERT, CACERT)
- provisioner, err := New(&issuer, key, cert, cacert)
+ issuer := createIssuerAndCerts(ISSUER_NAME, ISSUER_URL)
+ provisioner, err := New(&issuer, &certServiceClientMock{})
assert.Nil(t, err)
- assert.Equal(t, string(provisioner.key), string(key), "Unexpected provisioner key.")
- assert.Equal(t, string(provisioner.cert), string(cert), "Unexpected provisioner cert.")
- assert.Equal(t, string(provisioner.cacert), string(cacert), "Unexpected provisioner cacert.")
assert.Equal(t, provisioner.name, issuer.Name, "Unexpected provisioner name.")
assert.Equal(t, provisioner.url, issuer.Spec.URL, "Unexpected provisioner url.")
}
func Test_shouldSuccessfullyLoadPreviouslyStoredProvisioner(t *testing.T) {
- issuer, key, cert, cacert := createIssuerAndCerts(ISSUER_NAME, ISSUER_URL, KEY, CERT, CACERT)
- provisioner, err := New(&issuer, key, cert, cacert)
+ issuer := createIssuerAndCerts(ISSUER_NAME, ISSUER_URL)
+ provisioner, err := New(&issuer, &certServiceClientMock{})
assert.Nil(t, err)
@@ -67,19 +62,24 @@ func Test_shouldSuccessfullyLoadPreviouslyStoredProvisioner(t *testing.T) {
provisioner, ok := Load(issuerNamespaceName)
verifyThatConditionIsTrue(ok, "Provisioner could not be loaded.", t)
- assert.Equal(t, string(provisioner.key), string(key), "Unexpected provisioner key.")
- assert.Equal(t, string(provisioner.cert), string(cert), "Unexpected provisioner cert.")
- assert.Equal(t, string(provisioner.cacert), string(cacert), "Unexpected provisioner cacert.")
assert.Equal(t, provisioner.name, issuer.Name, "Unexpected provisioner name.")
assert.Equal(t, provisioner.url, issuer.Spec.URL, "Unexpected provisioner url.")
}
func Test_shouldReturnCorrectSignedPemsWhenParametersAreCorrect(t *testing.T) {
- const EXPECTED_SIGNED_FILENAME = "test_resources/expected_signed.pem"
- const EXPECTED_TRUSTED_FILENAME = "test_resources/expected_trusted.pem"
-
- issuer, key, cert, cacert := createIssuerAndCerts(ISSUER_NAME, ISSUER_URL, KEY, CERT, CACERT)
- provisioner, err := New(&issuer, key, cert, cacert)
+ const EXPECTED_SIGNED_FILENAME = "testdata/expected_signed.pem"
+ const EXPECTED_TRUSTED_FILENAME = "testdata/expected_trusted.pem"
+
+ issuer := createIssuerAndCerts(ISSUER_NAME, ISSUER_URL)
+ provisioner, err := New(&issuer, &certServiceClientMock{
+ getCertificatesFunc: func(csr []byte, pk []byte) (response *certserviceclient.CertificatesResponse, e error) {
+ mockResponse:= &certserviceclient.CertificatesResponse{
+ CertificateChain: []string{"cert-0", "cert-1"},
+ TrustedCertificates: []string{"trusted-cert-0", "trusted-cert-1"},
+ } //TODO: mock real certServiceClient response
+ return mockResponse, nil
+ },
+ })
issuerNamespaceName := createIssuerNamespaceName(ISSUER_NAMESPACE, ISSUER_NAME)
Store(issuerNamespaceName, provisioner)
@@ -91,7 +91,7 @@ func Test_shouldReturnCorrectSignedPemsWhenParametersAreCorrect(t *testing.T) {
ctx := context.Background()
request := createCertificateRequest()
- signedPEM, trustedCAs, err := provisioner.Sign(ctx, request)
+ signedPEM, trustedCAs, err := provisioner.Sign(ctx, request, nil)
assert.Nil(t, err)
@@ -112,11 +112,11 @@ func createIssuerNamespaceName(namespace string, name string) types.NamespacedNa
}
}
-func createIssuerAndCerts(name string, url string, key string, cert string, cacert string) (cmpv2api.CMPv2Issuer, []byte, []byte, []byte) {
+func createIssuerAndCerts(name string, url string) cmpv2api.CMPv2Issuer {
issuer := cmpv2api.CMPv2Issuer{}
issuer.Name = name
issuer.Spec.URL = url
- return issuer, []byte(key), []byte(cert), []byte(cacert)
+ return issuer
}
func readFile(filename string) []byte {
@@ -133,8 +133,8 @@ func createCertificateRequest() *cmapi.CertificateRequest {
const ISSUER_GROUP = "certmanager.onap.org"
const CONDITION_TYPE = "Ready"
- const SPEC_REQUEST_FILENAME = "test_resources/test_certificate_request.pem"
- const STATUS_CERTIFICATE_FILENAME = "test_resources/test_certificate.pem"
+ const SPEC_REQUEST_FILENAME = "testdata/test_certificate_request.pem"
+ const STATUS_CERTIFICATE_FILENAME = "testdata/test_certificate.pem"
duration := new(apimach.Duration)
d, _ := time.ParseDuration(CERTIFICATE_DURATION)
@@ -159,3 +159,11 @@ func createCertificateRequest() *cmapi.CertificateRequest {
func areSlicesEqual(slice1 []byte, slice2 []byte) bool {
return bytes.Compare(slice1, slice2) == 0
}
+
+type certServiceClientMock struct {
+ getCertificatesFunc func(csr []byte, key []byte) (*certserviceclient.CertificatesResponse, error)
+}
+
+func (client *certServiceClientMock) GetCertificates(csr []byte, key []byte) (*certserviceclient.CertificatesResponse, error) {
+ return client.getCertificatesFunc(csr, key)
+}
diff --git a/certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/expected_signed.pem b/certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/expected_signed.pem
index 2d0e84d4..2d0e84d4 100644
--- a/certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/expected_signed.pem
+++ b/certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/expected_signed.pem
diff --git a/certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/expected_trusted.pem b/certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/expected_trusted.pem
index 2d0e84d4..2d0e84d4 100644
--- a/certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/expected_trusted.pem
+++ b/certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/expected_trusted.pem
diff --git a/certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/test_certificate.pem b/certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/test_certificate.pem
index 7f306269..7f306269 100644
--- a/certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/test_certificate.pem
+++ b/certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/test_certificate.pem
diff --git a/certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/test_certificate_request.pem b/certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/test_certificate_request.pem
index 3becbf10..3becbf10 100644
--- a/certServiceK8sExternalProvider/src/cmpv2provisioner/test_resources/test_certificate_request.pem
+++ b/certServiceK8sExternalProvider/src/cmpv2provisioner/testdata/test_certificate_request.pem
diff --git a/certServiceK8sExternalProvider/src/testdata/constants.go b/certServiceK8sExternalProvider/src/testdata/constants.go
new file mode 100644
index 00000000..d2097bae
--- /dev/null
+++ b/certServiceK8sExternalProvider/src/testdata/constants.go
@@ -0,0 +1,12 @@
+package testdata
+
+import "encoding/base64"
+
+var (
+ KeyBytes, _ = base64.StdEncoding.DecodeString("QmFnIEF0dHJpYnV0ZXMKICAgIGZyaWVuZGx5TmFtZTogb29tLWNlcnQtc2VydmljZQogICAgbG9jYWxLZXlJRDogNTQgNjkgNkQgNjUgMjAgMzEgMzYgMzAgMzIgMzggMzMgMzkgMzIgMzIgMzAgMzcgMzkgMzUgCktleSBBdHRyaWJ1dGVzOiA8Tm8gQXR0cmlidXRlcz4KLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ3IwcWpSamNIb3lNY20KREJYanRlcm45V0pYWHlDQUlOREJ6SFV0MHR6WFFHbmlpSU9URXhldGdYdUl2KytoNEVrZ25SalVKNmVzSktFKwo3VHZnd21VS1hqc3ljY3pNWUhHMUE5bk5EN3BZcXAzY0dJZUp0ek9xV3hDVTh1MFhtMnRBVGI5Mm5VNHd0elpXCnhpbEJxa0pySmo3aThPbnVlaE0rR2tWUmZMUCtxUWJUUTVueE53dytlMlpMTWcyb0g3TDMwbTdhbTRxZ1hFL1IKdEdVcjU0a3dXZlVPQTN2MVlzd1daRjFZblhwRDRvRmdndGJZWWRVU1c5QnBTVzZITkRGeFFoMmtpQVJybU9rWgpXTGNiYzd6RnpSN0dIRzJlclhCODhZN3A3dFV5ZW5PUll6RkhnM0NEdGg3MUVUbCtUZjZITDZienBqc2E2UmNJClVyS1hxS3RuQWdNQkFBRUNnZ0VBTm9XMlJDaXphMmFxcXd3U3RoczMyenNtWllzdUNQcGd3OTVaSUoxVXJva20KRUZnNVNDWTYwVGZSTjJlUVp0R0E0dlIydUh1TTNUY1NZNkZyNnJwRXpiRnhIMlMxRS9WV241WUZPdWpPdk93SApBNXhWQmdJNFJzcDJ6SXo1WnhCT1RDMWZvQWZ5azhyUFYyR3lIY0FsSzFNTGlYL2crMmVKUzUrU2QzVVd1S3ZxCm1NQnJzeUhYeTZjR2p3dmlsdzBqYUUzOVhLNTVNRE95b1pNZUo4VDRlRkJBRXlvQm85amZYWjhrbXREM05PZVQKOTJYZW0rL2dnekRaMWtTWUVxOHBkZEpDRW9VZ2lzVmZ1ZUtKdDRNek9FcUtXOXNVbUp6NjZOK0FnT2hzbDU2VQo3dUhpdDRGV08rVmJtR0JQQ2hCSzMzR1hJNTZJRDZWakQyNllrWlNCd1FLQmdRRDlqTlk2cW9yWmdXbzhucnEyCk5VNzdRVmNyOVVzd2htQlM2UE1zR01XWGYveFg3cUVaeUlIbzIxTzg4TkZ4c2lXazUwZzY4UGVTTnQ0VGRyZ3kKTG5Ra3o4MzhWTk9ZdjNlQkZFWFY2RXFFUFY2VUtlZWU4RDYyMitET2JYaUtqS0tweVlqaXo4NmRPcUdLeTJjeApETlZGQnRDWEViRjErcEtmYWRtOU1YeS9PUUtCZ1FDdGU2c0Q4T0NOdyswSzMzK0xhUGg1Tlkyb0djNVZyNm5yCk4xNnlVV0lOVEFGVk1KZkFBUjlJNFpDR2V5RUw1UVNYOFpsRjlSOWNlVW9TaWJSMmdoOFV5dDQrZ0ZDU3JBSW8KUTBrWkhJeG9BYnp5MG9BanJINGtwNVd2QURld1ZCQ25aSmxXeGFjcUZMR2N0WTZRYW1PalpWVXVwSlVOQ2h6UwpwM2FMdWpZZm53S0JnRmpXNXgxSk1qdUIxK3FEcDJJK2pYMEY2UGhUQzFSbVVRdmI2WkJ5NFpEeTNFVW5MTFZ2CkJ1M0RJN1VaSUJuWlZNMVI2SUlXZW5oMTF4dzB4ZGQzWldTY2wwMHBuN1p1cC8zSFQ2emlwbkZ0VzExSXpZcG8KSFdGTzY1Y0l6bXFsV2oxcGl4Z0Z2aHhqTmNUKy9obzJwK2QydXRHajltMGpMZ3JET1BMTWl5d3BBb0dBRTU0ZgplYU9ja1F0dDI4UG9WV2g2YUtLQXNWaXh0NGpVeXkrSXV0dHZIaGZSTXNQNjlSQnJiRDl0cTNkekJqaFFxNm43CmJpakk4aGtaSWoyR05ieURMVU8vbkF2QWtNVjR2UHJXNGtzVEtaUEF2U2pHcXNJUHhhOVp3dDlnYk1VazJQa00KU2Y2eDU1Vk5mRzRmZi84MzR6dExSYW9BM09lZTJNZHRKV0hhU3ZzQ2dZRUF5b3dia2ZwdGFNSU9vbHg5Y2tmSwpEU0VNOEJtNUR1SVpqM1ZCWEdRZUtmMncvWHBXekR5SWRDdy9ZODBFNWRSNWlMZEhWS1BvOVJqeTY5bmpLeTNrCnJta2pzczMxa2dLaTFYR0FCMytTN2xmUE1sQnFCayt5WHVET1pWK3ZzZFFWR05xM1gwUFVkR09wV0g2VUtzTm8Kb3NOR042SHh0Z0NFaDUxdlNvT2tBY0U9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K")
+ NotMatchingKeyBytes, _ = base64.StdEncoding.DecodeString("QmFnIEF0dHJpYnV0ZXMKICAgIGZyaWVuZGx5TmFtZTogb29tLWNlcnQtc2VydmljZQogICAgbG9jYWxLZXlJRDogNTQgNjkgNkQgNjUgMjAgMzEgMzYgMzAgMzMgMzMgMzUgMzEgMzIgMzEgMzMgMzcgMzEgMzEgCktleSBBdHRyaWJ1dGVzOiA8Tm8gQXR0cmlidXRlcz4KLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQ29QeTljcE81a0NZUzQKSHpQY25UMVJkTkNYcis1Zk54VzlYUFEyUUNUYUJiWk9PeTJYbEVJdWVpWW5KUTQwb0VQelZYZWdxSXkwS1QyMwo3ZHJhSXlENkIyK282TmlsYkJLdlRzbnFkYytwRXdtRC96cXRTSUlhcEszNWl2SDNmcUZoZ0JZRWI2RkJSZzE0CkFDRlJwUWlkcVRzRHQyVFZmTEljU1ZIbElzRUU2UmRNcFgxN2ZQNVFjNUc4MCtONHp1b0tIQUV0V2xpWTBhZDYKT0k1TnlnMGNkK1VwZ0p6NFdPQVkwajV4a2Q0TWU3TW00TmlIMmZFa2pwdGdCbEVrTGo1bkVuQmVQRzhPaFBlYgphOVc3T1BXOTRodFc3RlN3QjMxWE1CMmhyZlJUNUZLanNhakgzSllRZkQrajdQc1d0QVhlYXc1dW93L1hVSEFnCnhwZHpxREtsQWdNQkFBRUNnZ0VBTnpNejJOT01HMm84RHlTOW9UNDlwZ0lMaGhrRXZseVdWMkN1QnphWVlLZkwKSG5Pd0M3RnEzVkRhUDdHYXJZMS9mZENteFNGSHVMeG5NWGRxZkhOV0dISmtJaWp6RzZNUHBsRFZVb1dBc2xvNgpud0lZUUU0djBZb0NRb05oeSsvU0J1azlVQXRIL3VCNi9zb2NKR3RmSGtzdHY1Um1FdDBzbXJjN2xWQUh3QklvCkcxOXlJeXdOai9nWS9LQmpuWElZaXgrS0kvR1AyUFNzZGJOTU5DNUtqMjc0NW1WNGRYaHJCNEhDWS9mMUcxWnIKa2gzRzZYeGF4ZEFJNm1pOFdIcXVFc1VzRkJKWFJLOTNNYkF1SU5GQllGQU15NitLWDY2N0lNdEVaQ3Yza2lUZQpmcjRDTmFHUDIwYk1pK0tYK2wzamdQMVBabDdhTVJWZkdPYlRXZk5hYVFLQmdRRHdSZHFiVFBXeEsySFh4bURVCnRDanp0SFRrWTZjNncxV3JpWlgxejVlNzVnRWtVVlYyZ3BnS1hXUUErbGVHVGFReUhGQ1NybmJ3TzVCSlF3clYKamEzWU1tWmJVeGVIczhLNHF1ZDlVeHFzciszaVdBdmJ6WGRaV1ZrdXFzS3ZReVFteGhMbkNTak5kUFRwY2t2ZApGWmI3REdNVGg3UnowT1gzSUdHbmxRWlNld0tCZ1FDelFtd2tkMTNKNndVd3A5U2NpNU15MzVIREdHQ1FVSW1FCkkzLzVlLy8raEpKckowcGhqS3hzR3FzT2R2QWg4SGlhZlZnS2hPTTZDRkh3WEZsN2hwdGZFWnRDMFpZUW9jR3oKR203ZWpOZmxROEJsSlU0VHNjSjREWHFzTS8zcTNZQ29aOTEzTWRXeW1ITDYwZkN5bWhyKytiMU5SVXVFSklUbQp5MUdmWm9xVlh3S0JnUUNGQTViZTdMOERZdW5hbzFjTnllTDE3NHZhdUJSbklxWTF2WWhJT2JGZGN4cGt1YVlmClluSzdJakp2bkNlQ0VVOTU4bFRrcnpMbkVZSnlIR3hPQnc0YnB1TWxZWjJnSVhNRitvOEd3VS8rRTdNVTMvdEkKcHJtUXZEYno3OUt4WmZFSWloVUMwLzVEcDZEQjcrVnhzamNRS2k1YkxJQklzWGZ0MUg1YllOUE1Ld0tCZ0NPQQo0RkpJb2ZhLzZZTTlla3FYQ2t4bEkwVjBxb3RxcUJIWHhoenZoa0F1bWFGSFl0LzNNSjhvbVFDQVpnY1N6WkFyCml5aHFNV2JwQnZHSUdPRHlSQXVNUFNmNndySUFsNUNWaDZma1ZVNC9JUGZuYjVOTy9ha3hZajZBL2FWcXdYU1oKUGEvQjJ3VklWRjJBL3g4Z2pvQTVqbGlqaE5Wak5qOVB2WmJJaEdJWkFvR0JBTWZiOXlvbGtmc3FmZXJ4bUJJYQp0NFZiVXFRNWRiMUp5ZFJkblduNWxGY1lmVzhScG5lOUtLQkFKeHZ5ME1laThOMHVidHl3Z0FDQXAzYyt2VUY1CjFYNWZIdzdPbUErclBjR0hRZENUeURVUitWKzM0L0RPRUJKencreXRLZVRUWHZrcXR3blBmWVQ1cVF4Uk93N20KMFZmWVQ2bXFtaXVySUU4b2VMUDR5dGp6Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K")
+ CertBytes, _ = base64.StdEncoding.DecodeString("QmFnIEF0dHJpYnV0ZXMKICAgIGZyaWVuZGx5TmFtZTogb29tLWNlcnQtc2VydmljZQogICAgbG9jYWxLZXlJRDogNTQgNjkgNkQgNjUgMjAgMzEgMzYgMzAgMzIgMzggMzMgMzkgMzIgMzIgMzAgMzcgMzkgMzUgCnN1YmplY3Q9QyA9IFVTLCBTVCA9IENhbGlmb3JuaWEsIEwgPSBTYW4tRnJhbmNpc2NvLCBPID0gTGludXgtRm91bmRhdGlvbiwgT1UgPSBPTkFQLCBDTiA9IG9uYXAub3JnCgppc3N1ZXI9QyA9IFVTLCBTVCA9IENhbGlmb3JuaWEsIEwgPSBTYW4tRnJhbmNpc2NvLCBPID0gTGludXgtRm91bmRhdGlvbiwgT1UgPSBPTkFQLCBDTiA9IG9uYXAub3JnCgotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJRkN6Q0NBdk9nQXdJQkFnSUVNOHQ3L2pBTkJna3Foa2lHOXcwQkFRd0ZBREIzTVFzd0NRWURWUVFHRXdKVgpVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVMVVp5WVc1amFYTmpiekVaCk1CY0dBMVVFQ2hNUVRHbHVkWGd0Um05MWJtUmhkR2x2YmpFTk1Bc0dBMVVFQ3hNRVQwNUJVREVSTUE4R0ExVUUKQXhNSWIyNWhjQzV2Y21jd0hoY05NakF4TURFMk1Ea3dOalU1V2hjTk1qRXhNREUyTURrd05qVTVXakIzTVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVMVVp5CllXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUVRHbHVkWGd0Um05MWJtUmhkR2x2YmpFTk1Bc0dBMVVFQ3hNRVQwNUIKVURFUk1BOEdBMVVFQXhNSWIyNWhjQzV2Y21jd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFSwpBb0lCQVFDcjBxalJqY0hveU1jbURCWGp0ZXJuOVdKWFh5Q0FJTkRCekhVdDB0elhRR25paUlPVEV4ZXRnWHVJCnYrK2g0RWtnblJqVUo2ZXNKS0UrN1R2Z3dtVUtYanN5Y2N6TVlIRzFBOW5ORDdwWXFwM2NHSWVKdHpPcVd4Q1UKOHUwWG0ydEFUYjkyblU0d3R6Wld4aWxCcWtKckpqN2k4T251ZWhNK0drVlJmTFArcVFiVFE1bnhOd3crZTJaTApNZzJvSDdMMzBtN2FtNHFnWEUvUnRHVXI1NGt3V2ZVT0EzdjFZc3dXWkYxWW5YcEQ0b0ZnZ3RiWVlkVVNXOUJwClNXNkhOREZ4UWgya2lBUnJtT2taV0xjYmM3ekZ6UjdHSEcyZXJYQjg4WTdwN3RVeWVuT1JZekZIZzNDRHRoNzEKRVRsK1RmNkhMNmJ6cGpzYTZSY0lVcktYcUt0bkFnTUJBQUdqZ1o0d2dac3dIUVlEVlIwT0JCWUVGRFowUi95TgpKTk1WbTRQQ0doNWNnV1NHVkQxOU1Da0dBMVVkRVFFQi93UWZNQjJDRUc5dmJTMWpaWEowTFhObGNuWnBZMldDCkNXeHZZMkZzYUc5emREQVBCZ05WSFJNRUNEQUdBUUgvQWdFQU1COEdBMVVkSXdRWU1CYUFGR1FORGR2aWpRSG8KUzZtTy9nakplZ1h0MC9jc01CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBakFOQmdrcQpoa2lHOXcwQkFRd0ZBQU9DQWdFQU9aQ1dZMXQvUFpGRWN5Y3h2T09jWW9qb0ZlcVZhdE01MHVxNVJuS2c4UG1RCkFMTGN1SGVIVVIyS2RIZ0ZYajRBeWt6V1JDU1pwdjZsekZ3WStpVUUxaWF2MENHcWR5Mml6Vkx2ampvUDFoRHQKQXRVQjg1VnBrblJwdlFvYldwN0s2TUhOMXRlUWlnZ0hBNjhqQXp1UlMwL2pUUGR6MTdXNGVkdHBrMHQ4QlRaUgpCUG9rM1VWa1JBZGEzTElFWHh0YXlaaGpuSU92WXNnZGllU2lha0VOaDFzNTRqd3B2bU5KKzk5WU91WDcrTW00ClY1UmNGZGJKRHo0UjJUaUZETEJ1d2ZXaFJUei9Ga1MrYTlvaElhc3l2MmVHM0Q1dlVGeENyZzYydWQ2c25nakgKOFRaZnpJQXFhZEluVkIxcXF2dHgrRGVZMGgxRUZUWkZiWHM2WVpLVzhuL3pjQ1FEVERSOS9yeVczNVFKdWozbgpxa1ZsTnBnRGJVRlVucXEySUl0dFNrS21vMVg2QXJRZkwva0VGNWV5YnpzdFlnZkIyZkx1ZmlobDAyNGJHY1Y3Ci9JSkNDRFEreUg5ZDJDWjJmTW1ueFppRHkyYW1BRU5nT3UxRlZLUU1UODErTjVKVFZJWFFqeEFPbnJLWGFLY2wKQXFuWHRhUEh5SUVoU0ZhL2RBRExMd2pkT2xjZ3hpMUt1UDEzTXU1c0JQWEIzVWx2ZEYyYXMzTnRjaEZOaXAzcwoxeGExcDRUejhWTFUwaXE2YmltczVlczVsVFlQd2JmZTBoTnljcHZ1NFhyTXk3MHNnYWJ3L1NMTWlHeDVUVGg0CkQ2Qkgya0VwNDUxVnUwZnFyU0xmU21aQVNaenBYZ3lSQmlUMys0c2E1QlhwaHZCd1pOeExpUzV0ZGJONHd2QT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQpCYWcgQXR0cmlidXRlcwogICAgZnJpZW5kbHlOYW1lOiBDTj1vbmFwLm9yZyxPVT1PTkFQLE89TGludXgtRm91bmRhdGlvbixMPVNhbi1GcmFuY2lzY28sU1Q9Q2FsaWZvcm5pYSxDPVVTCnN1YmplY3Q9QyA9IFVTLCBTVCA9IENhbGlmb3JuaWEsIEwgPSBTYW4tRnJhbmNpc2NvLCBPID0gTGludXgtRm91bmRhdGlvbiwgT1UgPSBPTkFQLCBDTiA9IG9uYXAub3JnCgppc3N1ZXI9QyA9IFVTLCBTVCA9IENhbGlmb3JuaWEsIEwgPSBTYW4tRnJhbmNpc2NvLCBPID0gTGludXgtRm91bmRhdGlvbiwgT1UgPSBPTkFQLCBDTiA9IG9uYXAub3JnCgotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJRm5qQ0NBNGFnQXdJQkFnSUVHSEJiNkRBTkJna3Foa2lHOXcwQkFRd0ZBREIzTVFzd0NRWURWUVFHRXdKVgpVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVMVVp5WVc1amFYTmpiekVaCk1CY0dBMVVFQ2hNUVRHbHVkWGd0Um05MWJtUmhkR2x2YmpFTk1Bc0dBMVVFQ3hNRVQwNUJVREVSTUE4R0ExVUUKQXhNSWIyNWhjQzV2Y21jd0hoY05NakF4TURFMk1Ea3dOalV5V2hjTk16QXhNREUwTURrd05qVXlXakIzTVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVMVVp5CllXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUVRHbHVkWGd0Um05MWJtUmhkR2x2YmpFTk1Bc0dBMVVFQ3hNRVQwNUIKVURFUk1BOEdBMVVFQXhNSWIyNWhjQzV2Y21jd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJSwpBb0lDQVFDREJSMDZTU1BteFVpdWc1NC9Ya1piVFN2ZTE4M2VEYityT2JBT1d1MWMzeVFIQmpCQUFFQ2E0SXVxClRaR3dOb0svdllYcjJpcnlRMDJMcHA3N3pCQ3lwVkNEckhHbDE1d0hwa0NZak5Ob3Vrb1lIaGErdkNFc3RubGgKVExCUHllclFwZGNlckhzVVRhSHBoamRrcGZMa2xGcmZGejZTQ28xa3ZJbmdoRkFFUmxqT2FOMy9pcTI3MUlBVAplcHlBVkRkVHpRK3h6TUJOUUZnRjNRVU9SaDE2NUlKNFFkOVpWY1hjakd3SUxHVjlsdzRBYUlTalZxSXBrYkxoCnB3am5BNFBtTGRadkhyN3l6VDVHTXhQWTdRVjkvN05RZmtuT1RPU1pxRlgyZHBzcVhkN21Odi9HMDgxekRiSloKYmR5VUh5QXFQbTRJN3JaKzZmckg3OFBvQ0h3QXAxbU9QNUF6VEtFWVVlbjFQQis4OGxUcWxtanhuOFZYejh2Tgo1NWZJNFlDUUg2dGxSdXdRamwxSFF5SVBEWGpoOE9KSUluNElnOWF5OUZNOUNTL0p3N0hrT2JqSW1oQU0yTW5RCkpuQ0FzT3ZYeW40amRic0dpaFpvWEYzODdPZ0x0V0NqendaWk1NQk84Rm5ibmNZZ1dlY2JuWXBFcnI3Tlp4cjEKRTNxQjNKVHNZNVRBSW1OM052ckZJeXVvdmY1NGR5ckRXUWh0bGUwY3VuZUJCUzU3SFNnWFNlRGp2Vko4V3I1MQpwZlJrZE1CbkE0Wll4SmRaamtpVzFvY1RJZXh3V2sxdVBtMC93RFVsVytwcHlzS0hUNXAyOTBOa3RSVWNCMGJ4ClA0YzkzOEl1bUNOZU5ZT1dpUENBcGVDUmlmODYwTG5oMWQzVHFHL1dQMGJUY1gySEFRSURBUUFCb3pJd01EQWQKQmdOVkhRNEVGZ1FVWkEwTjIrS05BZWhMcVk3K0NNbDZCZTNUOXl3d0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTgpCZ2txaGtpRzl3MEJBUXdGQUFPQ0FnRUFNMVFEaEM2ZEp6d0VlMHNmOHg2aXArYy9MSEFFbE9PV1g3K04vUVJ1CmlaYWNjZmdveDZhZHU0QkUrbDltVXJxS3hGQm5wb216dm9MZlNyc09ramhqMUc1dU9qSXV4QVJabktyY3dJNGoKYytXdWNTcUJCbkRxeXpMNys3RzFVbm01K3lpZmw1QUVzMngrN2Z0RnpvZ1VLV2E5M3hzUTIyYU5nRE96MCtCNgpGTCtWUEMwSlNMSDJRR1R0SEpWTUtpTEtBajFNMXJBc2l1SVNLNUtLbWs5Q0dGSmwySEFnV0s0TFQ0Y2JaUFQ4CjJCT0RMS2FXMnFGVlJKUkNSSlVCOUhaR0R6K0ZuOU14TlhtUWYwb3grK0h5Y0psZ1FNc0lET1VKajZCOGJ5Z0kKZVUwcEQ1MFJNS05DK3RuYWVITFJLTHJHQTFLYVc5a2t1MFVPL2RJTk1kbUlmaTlGS0diVUFvTGgvMmNSMWJVRgpYUXRpS2VLc0gvMEhXUS9NMmlVcEhhU1NneCt4ek54LzR3YU9MOFdkWmF0amVxVmNiUkhseS9sazZtcStXRTgxCjM4aTlyTVpNaUh3VktoYnp1d1ltUTRHTHVBZFEvUnR0clVMTTEvNEZvaE1oVXBsdnVndngrZmFqcVlmODVrTlIKb2tkdUZ4T1h0M01jMnJHZHRqby9jQ3VyMXN5S2NqWG5CK3NZbXpBYlAyWlNrRDBMbStGN2RwUDJHNDZmTC9hSwpUVExIUktxVlNHekNpekN1a1BNT2RvL0xqQnhyT01VZHVCdW5nU0VucHFHT0NHdzhuLzdkalZJbGw3ZURtaThkCmM0b25kOGN6YnFMaGNNZ2FVa2o2aFUySUR4R1ROL0hzeHh4OHE1TVN3NTdwSUF2dVQ2b0h4RUN1R0pINHRpVTkKUVM0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==")
+ CacertBytes, _ = base64.StdEncoding.DecodeString("QmFnIEF0dHJpYnV0ZXMKICAgIGZyaWVuZGx5TmFtZTogcm9vdAogICAgMi4xNi44NDAuMS4xMTM4OTQuNzQ2ODc1LjEuMTogPFVuc3VwcG9ydGVkIHRhZyA2PgpzdWJqZWN0PUMgPSBVUywgU1QgPSBDYWxpZm9ybmlhLCBMID0gU2FuLUZyYW5jaXNjbywgTyA9IExpbnV4LUZvdW5kYXRpb24sIE9VID0gT05BUCwgQ04gPSBvbmFwLm9yZwoKaXNzdWVyPUMgPSBVUywgU1QgPSBDYWxpZm9ybmlhLCBMID0gU2FuLUZyYW5jaXNjbywgTyA9IExpbnV4LUZvdW5kYXRpb24sIE9VID0gT05BUCwgQ04gPSBvbmFwLm9yZwoKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZuakNDQTRhZ0F3SUJBZ0lFR0hCYjZEQU5CZ2txaGtpRzl3MEJBUXdGQURCM01Rc3dDUVlEVlFRR0V3SlYKVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1TFVaeVlXNWphWE5qYnpFWgpNQmNHQTFVRUNoTVFUR2x1ZFhndFJtOTFibVJoZEdsdmJqRU5NQXNHQTFVRUN4TUVUMDVCVURFUk1BOEdBMVVFCkF4TUliMjVoY0M1dmNtY3dIaGNOTWpBeE1ERTJNRGt3TmpVeVdoY05NekF4TURFME1Ea3dOalV5V2pCM01Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1TFVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFUR2x1ZFhndFJtOTFibVJoZEdsdmJqRU5NQXNHQTFVRUN4TUVUMDVCClVERVJNQThHQTFVRUF4TUliMjVoY0M1dmNtY3dnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDRHdBd2dnSUsKQW9JQ0FRQ0RCUjA2U1NQbXhVaXVnNTQvWGtaYlRTdmUxODNlRGIrck9iQU9XdTFjM3lRSEJqQkFBRUNhNEl1cQpUWkd3Tm9LL3ZZWHIyaXJ5UTAyTHBwNzd6QkN5cFZDRHJIR2wxNXdIcGtDWWpOTm91a29ZSGhhK3ZDRXN0bmxoClRMQlB5ZXJRcGRjZXJIc1VUYUhwaGpka3BmTGtsRnJmRno2U0NvMWt2SW5naEZBRVJsak9hTjMvaXEyNzFJQVQKZXB5QVZEZFR6USt4ek1CTlFGZ0YzUVVPUmgxNjVJSjRRZDlaVmNYY2pHd0lMR1Y5bHc0QWFJU2pWcUlwa2JMaApwd2puQTRQbUxkWnZIcjd5elQ1R014UFk3UVY5LzdOUWZrbk9UT1NacUZYMmRwc3FYZDdtTnYvRzA4MXpEYkpaCmJkeVVIeUFxUG00STdyWis2ZnJINzhQb0NId0FwMW1PUDVBelRLRVlVZW4xUEIrODhsVHFsbWp4bjhWWHo4dk4KNTVmSTRZQ1FINnRsUnV3UWpsMUhReUlQRFhqaDhPSklJbjRJZzlheTlGTTlDUy9KdzdIa09iakltaEFNMk1uUQpKbkNBc092WHluNGpkYnNHaWhab1hGMzg3T2dMdFdDanp3WlpNTUJPOEZuYm5jWWdXZWNibllwRXJyN05aeHIxCkUzcUIzSlRzWTVUQUltTjNOdnJGSXl1b3ZmNTRkeXJEV1FodGxlMGN1bmVCQlM1N0hTZ1hTZURqdlZKOFdyNTEKcGZSa2RNQm5BNFpZeEpkWmpraVcxb2NUSWV4d1drMXVQbTAvd0RVbFcrcHB5c0tIVDVwMjkwTmt0UlVjQjBieApQNGM5MzhJdW1DTmVOWU9XaVBDQXBlQ1JpZjg2MExuaDFkM1RxRy9XUDBiVGNYMkhBUUlEQVFBQm96SXdNREFkCkJnTlZIUTRFRmdRVVpBME4yK0tOQWVoTHFZNytDTWw2QmUzVDl5d3dEd1lEVlIwVEFRSC9CQVV3QXdFQi96QU4KQmdrcWhraUc5dzBCQVF3RkFBT0NBZ0VBTTFRRGhDNmRKendFZTBzZjh4NmlwK2MvTEhBRWxPT1dYNytOL1FSdQppWmFjY2Znb3g2YWR1NEJFK2w5bVVycUt4RkJucG9tenZvTGZTcnNPa2poajFHNXVPakl1eEFSWm5LcmN3STRqCmMrV3VjU3FCQm5EcXl6TDcrN0cxVW5tNSt5aWZsNUFFczJ4KzdmdEZ6b2dVS1dhOTN4c1EyMmFOZ0RPejArQjYKRkwrVlBDMEpTTEgyUUdUdEhKVk1LaUxLQWoxTTFyQXNpdUlTSzVLS21rOUNHRkpsMkhBZ1dLNExUNGNiWlBUOAoyQk9ETEthVzJxRlZSSlJDUkpVQjlIWkdEeitGbjlNeE5YbVFmMG94KytIeWNKbGdRTXNJRE9VSmo2QjhieWdJCmVVMHBENTBSTUtOQyt0bmFlSExSS0xyR0ExS2FXOWtrdTBVTy9kSU5NZG1JZmk5RktHYlVBb0xoLzJjUjFiVUYKWFF0aUtlS3NILzBIV1EvTTJpVXBIYVNTZ3greHpOeC80d2FPTDhXZFphdGplcVZjYlJIbHkvbGs2bXErV0U4MQozOGk5ck1aTWlId1ZLaGJ6dXdZbVE0R0x1QWRRL1J0dHJVTE0xLzRGb2hNaFVwbHZ1Z3Z4K2ZhanFZZjg1a05SCm9rZHVGeE9YdDNNYzJyR2R0am8vY0N1cjFzeUtjalhuQitzWW16QWJQMlpTa0QwTG0rRjdkcFAyRzQ2ZkwvYUsKVFRMSFJLcVZTR3pDaXpDdWtQTU9kby9MakJ4ck9NVWR1QnVuZ1NFbnBxR09DR3c4bi83ZGpWSWxsN2VEbWk4ZApjNG9uZDhjemJxTGhjTWdhVWtqNmhVMklEeEdUTi9Ic3h4eDhxNU1TdzU3cElBdnVUNm9IeEVDdUdKSDR0aVU5ClFTND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=")
+ CsrBytes, _ = base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQy9EQ0NBZVFDQVFBd2JqRUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdUQ2tOaGJHbG1iM0p1YVdFeApDekFKQmdOVkJBY1RBbFZUTVEwd0N3WURWUVFLRXdSdmJtRndNUkF3RGdZRFZRUUxFd2R2Ym1Gd0xXOTFNUnd3CkdnWURWUVFERXhOalpYSjBhWE56ZFdWeUxtOXVZWEF1YjNKbk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0MKQVE4QU1JSUJDZ0tDQVFFQTJSZDFCV3JIRm5wUGdkdy9VaFBOYWZPWjB1S3lGZytuWHNNQUJEbFB6YzBxaWRWWQp4RTVmU0E0dUFXUmpvV1FLQ1dxQWxGZS9LYjBSL3ZlTTN6K0hUMUQrRXBsNjNZWUF5dVYyNFdaa2JEeDhGdGV6CjBqd2l1R21Zb0lld1JXMmZXY25RcTV6WDZ4LzEyalJ3eThtQVpJaFRtUXloTjRQWGpjd09ZcVJrVTBQeGsrQnAKMmt4RWpTQi9SSlJGKzE5YWh6K2IreStLQ0dJRVdiSStXb0RpMzFNYmh0aVVrMnd2MXdzUk8vbmRnM1RxT2Y0Twp5QjNtVGlYMkIyL0t1ZXpLbFlseENobUdjS1UxdTUwU0pYT3JZYU9KNTZJemdDTU1FVk1YaEpzYlgweFlnMkZMCjZsSkxXcjlma3pxeFRmenMxYUdVWXdDcG9rWHROa1UybXBPT1lRSURBUUFCb0Vrd1J3WUpLb1pJaHZjTkFRa08KTVRvd09EQXBCZ05WSFJFRUlqQWdnZ2xzYjJOaGJHaHZjM1NDRTJObGNuUnBjM04xWlhJdWIyNWhjQzV2Y21jdwpDd1lEVlIwUEJBUURBZ1dnTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDNThJWkt5dlFwdUFuVHR3NUd1eVh2ClQralNML240Kzg1b1dwamwxYVZnQTVRcWVHWU8wRzVQbzNMbGVLeTlCaEJIclBmY201eEFudFUvM0VKWmJBSFQKai9WWkRzdmVaR3JEc2hqRWI4dmNuSTRROXVpY0dNYnlUbktFcVpzSm5EMlpqN1RiWFBocXQrV1Z5S0RJc0ZLdAprNHVSWGpaRTI0VVh6ZFhiWnNUeWlscFl0RzJkR3RTTXVSdll6NlR1eUlWRVZMNVBXRWRGak5VUVJSK2czTVpDCmtrc2pKSzkvSC9OZVl1TS9QN1BUWjZkRWFUY3c5UmtSdEVGazBPQlRxWUlyaUhmczJUMUlIdzdMaVl6NFhyUEQKVFBncHppM1IyZVFINDhzSDVMS1UvblQxUWtNbHZiS3RpcWdoZkx6eGQ0MXEzRTZxNkRZdGJybHN6eTVpeTFqOAotLS0tLUVORCBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0K")
+ PkBytes, _ = base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRFpGM1VGYXNjV2VrK0IKM0Q5U0U4MXA4NW5TNHJJV0Q2ZGV3d0FFT1UvTnpTcUoxVmpFVGw5SURpNEJaR09oWkFvSmFvQ1VWNzhwdlJIKwo5NHpmUDRkUFVQNFNtWHJkaGdESzVYYmhabVJzUEh3VzE3UFNQQ0s0YVppZ2g3QkZiWjlaeWRDcm5OZnJIL1hhCk5IREx5WUJraUZPWkRLRTNnOWVOekE1aXBHUlRRL0dUNEduYVRFU05JSDlFbEVYN1gxcUhQNXY3TDRvSVlnUloKc2o1YWdPTGZVeHVHMkpTVGJDL1hDeEU3K2QyRGRPbzUvZzdJSGVaT0pmWUhiOHE1N01xVmlYRUtHWVp3cFRXNwpuUklsYzZ0aG80bm5vak9BSXd3UlV4ZUVteHRmVEZpRFlVdnFVa3RhdjErVE9yRk4vT3pWb1pSakFLbWlSZTAyClJUYWFrNDVoQWdNQkFBRUNnZ0VBWk04NHZ5QTdmUnVsQ2hlZHE5NllOOGd3T1RhZUhoSjgxVXRXR2FBSGgvanEKOVFDR2JQbzcwcmtLOGdpTkgyZldKVk00akNwSEVmbkRmcE96N2dPUk1PcmFZUWEyZ0dIMndrRldPQXNWUFJIRgpTZEkycGJ6WkhxdWlmWUVsQU1pTUErVHNxcFIxeTdDV3VSSTdBdGI2Y1RUQkpVUXhKUmRySkdTS2xaSGpLS3A4Clo4V0xkNllqTnVzSlY0c1ZNb0pTR3RPc0tkcUx0RytlYnhPK2RCdWNnbm95S01KMi9LREl2bmxHWE1CME5IOUYKaXVOSVFYNnlReHI0VlNGSVZndldwZ0dPYTI0QWtxSlFGRm80UVdlc1JVa01EaFU5aTJPYWc4ekNxL1U4VmFpMQo2eTBTa2FucTBxbVFoN1c5UFV5RXlKdGhhbk5RczYzNDZXWkZmNWY0bFFLQmdRRHc1MzRLZFNiYkJjQTBZVmpaCjI1NDRLbE1MMzBSc04vN2JIdEt0djVVKzIyYjBXekRycHVVbFpVUmVFZGducGlBQVg3RUpLdFRDUVVCektuTmQKU1g4WTE4WTB6cEkyZnhJb2pjN0YwVy9aMzB0bkM1NjNKLzV0WTk4eVJzQ3lISXpRTXQvOTMzUVRLU2pGaktBcwp1ZCtKbDR6Q25yWUpnVitrTGV6V1R4c1ptd0tCZ1FEbXNmaGZPOUF6V3diQS9IS09aL01ZaTRjcWE5disyN2xSCktmYStZQ3ZMRUE0UWhJRTZXVFA2ZGFtTnpwZytQMW9QcmdmSVBpNVJOelRmdlFoSzlMaW56dDdxVTJYbWJYVXUKNDJpR2p3UklwY1hLaGxYeFNCTTFQVGZ5dW5GaXlORW1qVlR2SWdudW9vNmdJNUp0RVNLQ2hGZy95YWIwRm9ONQpVRnV1MGp2bHN3S0JnRUpKWUZ3ZVNqZkFDRmdoWlNKbEZNOGRqa1paQStuSEtxQStoZmY3SEdUMFdBcnF3TFpHCjhReHVKZmJBY0RyUXNrT0lFUjJWcEg5akZ3blpaMjhHMXlzTnpHTWhhQWdJeFFWVnA4eTB5Vk1vNXdXT28vaC8KejdsbjNyVmwxSVh0NXkwdW9vV25vN2ZWL25zRks5bkN0MmlUdzg2VmZ6OTBVczNKT1Q3cSsyajdBb0dBUVFpTQp0dlFhcGsrVDROV0p5Y0ZlRTE1S0ZWaGdwVUQxeGY2cGMxT1RKT1I2d29kSUV0WFF4RnRsRi9mVWpUKzR1TkRiCm1zU0V0QnAzQ2xlMHZjU3RSWWtZNkQvb2F3UVNVOHlCeStVSFZSOStXYkJ6QzlqQXFYSi9raXFqQ2pFSVhQRGMKcjZrTjJicnpzQXMzSFE0R2gzcWRraVhicmRXbTdJME51NFBDcE9jQ2dZRUFzNUh1aFZ3Q0lrL0IrY0I0Um5RbwpXWTJOQjVPd1FpbmVmd3RVVlJJTWxGTkhWeWljTmhyME5wQkJ0TGF0RFRZOTlYRmx3eHh3LzMrM0hBbUdxTjVvCmNvVTVkQ3dNRWo0RmZ6V3ZIUTBWa0VsRVRkZ3ZnVFluejFYU015alJXZjZweTRaTXpBZ0xJL2pDQXlGMnQvMVMKZ1ZIR01LRFV0YjdNRHIzamg3ZmxoUG89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K")
+)