Remove kubecertauthority pkg
All of its functionality was refactored to move elsewhere or to not be needed anymore by previous commits
This commit is contained in:
parent
69137fb6b9
commit
3f06be2246
@ -1,262 +0,0 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package kubecertauthority implements a signer backed by the kubernetes controller-manager signing
|
||||
// keys (accessed via the kubernetes Exec API).
|
||||
package kubecertauthority
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/deprecated/scheme"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"go.pinniped.dev/internal/certauthority"
|
||||
"go.pinniped.dev/internal/constable"
|
||||
)
|
||||
|
||||
// ErrNoKubeCertAgentPod is returned when no kube-cert-agent pod is found on the cluster.
|
||||
const ErrNoKubeCertAgentPod = constable.Error("did not find kube-cert-agent pod")
|
||||
const ErrIncapableOfIssuingCertificates = constable.Error("this cluster is not currently capable of issuing certificates")
|
||||
|
||||
const k8sAPIServerCACertPEMDefaultPath = "/etc/kubernetes/ca/ca.pem"
|
||||
const k8sAPIServerCAKeyPEMDefaultPath = "/etc/kubernetes/ca/ca.key"
|
||||
|
||||
type signer interface {
|
||||
IssuePEM(subject pkix.Name, dnsNames []string, ttl time.Duration) ([]byte, []byte, error)
|
||||
}
|
||||
|
||||
type PodCommandExecutor interface {
|
||||
Exec(podNamespace string, podName string, commandAndArgs ...string) (stdoutResult string, err error)
|
||||
}
|
||||
|
||||
type kubeClientPodCommandExecutor struct {
|
||||
kubeConfig *restclient.Config
|
||||
kubeClient kubernetes.Interface
|
||||
}
|
||||
|
||||
func NewPodCommandExecutor(kubeConfig *restclient.Config, kubeClient kubernetes.Interface) PodCommandExecutor {
|
||||
return &kubeClientPodCommandExecutor{kubeConfig: kubeConfig, kubeClient: kubeClient}
|
||||
}
|
||||
|
||||
func (s *kubeClientPodCommandExecutor) Exec(podNamespace string, podName string, commandAndArgs ...string) (string, error) {
|
||||
request := s.kubeClient.
|
||||
CoreV1().
|
||||
RESTClient().
|
||||
Post().
|
||||
Namespace(podNamespace).
|
||||
Resource("pods").
|
||||
Name(podName).
|
||||
SubResource("exec").
|
||||
VersionedParams(&v1.PodExecOptions{
|
||||
Stdin: false,
|
||||
Stdout: true,
|
||||
Stderr: false,
|
||||
TTY: false,
|
||||
Command: commandAndArgs,
|
||||
}, scheme.ParameterCodec)
|
||||
|
||||
executor, err := remotecommand.NewSPDYExecutor(s.kubeConfig, "POST", request.URL())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var stdoutBuf bytes.Buffer
|
||||
if err := executor.Stream(remotecommand.StreamOptions{Stdout: &stdoutBuf}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return stdoutBuf.String(), nil
|
||||
}
|
||||
|
||||
// AgentInfo is a data object that holds the fields necessary for a CA to communicate with an agent
|
||||
// pod.
|
||||
type AgentInfo struct {
|
||||
// Namespace is the namespace in which the agent pod is running.
|
||||
Namespace string
|
||||
// LabelSelector is a label selector (e.g., "label-key=label=value") that can be used to filter
|
||||
// the agent pods.
|
||||
LabelSelector string
|
||||
// CertPathAnnotation is the annotation used by the agent pod to indicate the path to the CA cert
|
||||
// inside the pod.
|
||||
CertPathAnnotation string
|
||||
// KeyPathAnnotation is the annotation used by the agent pod to indicate the path to the CA key
|
||||
// inside the pod.
|
||||
KeyPathAnnotation string
|
||||
}
|
||||
|
||||
type CA struct {
|
||||
agentInfo *AgentInfo
|
||||
|
||||
kubeClient kubernetes.Interface
|
||||
podCommandExecutor PodCommandExecutor
|
||||
|
||||
shutdown, done chan struct{}
|
||||
|
||||
onSuccessfulRefresh SuccessCallback
|
||||
onFailedRefresh FailureCallback
|
||||
|
||||
lock sync.RWMutex
|
||||
activeSigner signer
|
||||
}
|
||||
|
||||
type ShutdownFunc func()
|
||||
type SuccessCallback func()
|
||||
type FailureCallback func(error)
|
||||
|
||||
// New creates a new instance of a CA. It tries to load the kube API server's private key
|
||||
// immediately. If that succeeds then it calls the success callback and it is ready to issue certs.
|
||||
// When it fails to get the kube API server's private key, then it calls the failure callback and
|
||||
// it will try again on the next tick. It starts a goroutine to periodically reload the kube
|
||||
// API server's private key in case it failed previously or case the key has changed. It returns
|
||||
// a function that can be used to shut down that goroutine. Future attempts made by that goroutine
|
||||
// to get the key will also result in success or failure callbacks.
|
||||
//
|
||||
// The CA will try to read (via cat(1)) the kube API server's private key from an agent pod located
|
||||
// via the provided agentInfo.
|
||||
func New(
|
||||
agentInfo *AgentInfo,
|
||||
kubeClient kubernetes.Interface,
|
||||
podCommandExecutor PodCommandExecutor,
|
||||
tick <-chan time.Time,
|
||||
onSuccessfulRefresh SuccessCallback,
|
||||
onFailedRefresh FailureCallback,
|
||||
) (*CA, ShutdownFunc) {
|
||||
signer, err := createSignerWithAPIServerSecret(agentInfo, kubeClient, podCommandExecutor)
|
||||
if err != nil {
|
||||
klog.Errorf("could not initially fetch the API server's signing key: %s", err)
|
||||
signer = nil
|
||||
onFailedRefresh(err)
|
||||
} else {
|
||||
onSuccessfulRefresh()
|
||||
}
|
||||
result := &CA{
|
||||
agentInfo: agentInfo,
|
||||
kubeClient: kubeClient,
|
||||
podCommandExecutor: podCommandExecutor,
|
||||
shutdown: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
onSuccessfulRefresh: onSuccessfulRefresh,
|
||||
onFailedRefresh: onFailedRefresh,
|
||||
activeSigner: signer,
|
||||
}
|
||||
go result.refreshLoop(tick)
|
||||
return result, result.shutdownRefresh
|
||||
}
|
||||
|
||||
func createSignerWithAPIServerSecret(
|
||||
agentInfo *AgentInfo,
|
||||
kubeClient kubernetes.Interface,
|
||||
podCommandExecutor PodCommandExecutor,
|
||||
) (signer, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pod, err := findCertAgentPod(ctx, kubeClient, agentInfo.Namespace, agentInfo.LabelSelector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPath, keyPath := getKeypairFilePaths(pod, agentInfo)
|
||||
|
||||
certPEM, err := podCommandExecutor.Exec(pod.Namespace, pod.Name, "cat", certPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyPEM, err := podCommandExecutor.Exec(pod.Namespace, pod.Name, "cat", keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return certauthority.Load(certPEM, keyPEM)
|
||||
}
|
||||
|
||||
func (c *CA) refreshLoop(tick <-chan time.Time) {
|
||||
for {
|
||||
select {
|
||||
case <-c.shutdown:
|
||||
close(c.done)
|
||||
return
|
||||
case <-tick:
|
||||
c.updateSigner()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CA) updateSigner() {
|
||||
newSigner, err := createSignerWithAPIServerSecret(
|
||||
c.agentInfo,
|
||||
c.kubeClient,
|
||||
c.podCommandExecutor,
|
||||
)
|
||||
if err != nil {
|
||||
klog.Errorf("could not create signer with API server secret: %s", err)
|
||||
c.onFailedRefresh(err)
|
||||
return
|
||||
}
|
||||
c.lock.Lock()
|
||||
c.activeSigner = newSigner
|
||||
c.lock.Unlock()
|
||||
c.onSuccessfulRefresh()
|
||||
}
|
||||
|
||||
func (c *CA) shutdownRefresh() {
|
||||
close(c.shutdown)
|
||||
<-c.done
|
||||
}
|
||||
|
||||
// IssuePEM issues a new server certificate for the given identity and duration, returning it as a pair of
|
||||
// PEM-formatted byte slices for the certificate and private key.
|
||||
func (c *CA) IssuePEM(subject pkix.Name, dnsNames []string, ttl time.Duration) ([]byte, []byte, error) {
|
||||
c.lock.RLock()
|
||||
signer := c.activeSigner
|
||||
c.lock.RUnlock()
|
||||
|
||||
if signer == nil {
|
||||
return nil, nil, ErrIncapableOfIssuingCertificates
|
||||
}
|
||||
|
||||
return signer.IssuePEM(subject, dnsNames, ttl)
|
||||
}
|
||||
|
||||
func findCertAgentPod(ctx context.Context, kubeClient kubernetes.Interface, namespace, labelSelector string) (*v1.Pod, error) {
|
||||
pods, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: labelSelector,
|
||||
FieldSelector: "status.phase=Running",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not check for kube-cert-agent pod: %w", err)
|
||||
}
|
||||
for _, pod := range pods.Items {
|
||||
return &pod, nil
|
||||
}
|
||||
return nil, ErrNoKubeCertAgentPod
|
||||
}
|
||||
|
||||
func getKeypairFilePaths(pod *v1.Pod, agentInfo *AgentInfo) (string, string) {
|
||||
annotations := pod.Annotations
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
certPath, ok := annotations[agentInfo.CertPathAnnotation]
|
||||
if !ok {
|
||||
certPath = k8sAPIServerCACertPEMDefaultPath
|
||||
}
|
||||
|
||||
keyPath, ok := annotations[agentInfo.KeyPathAnnotation]
|
||||
if !ok {
|
||||
keyPath = k8sAPIServerCAKeyPEMDefaultPath
|
||||
}
|
||||
|
||||
return certPath, keyPath
|
||||
}
|
@ -1,426 +0,0 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubecertauthority
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sclevine/spec"
|
||||
"github.com/sclevine/spec/report"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
)
|
||||
|
||||
type fakePodExecutor struct {
|
||||
resultsToReturn []string
|
||||
errorsToReturn []error
|
||||
|
||||
calledWithPodName []string
|
||||
calledWithPodNamespace []string
|
||||
calledWithCommandAndArgs [][]string
|
||||
|
||||
callCount int
|
||||
}
|
||||
|
||||
func (s *fakePodExecutor) Exec(podNamespace string, podName string, commandAndArgs ...string) (string, error) {
|
||||
s.calledWithPodNamespace = append(s.calledWithPodNamespace, podNamespace)
|
||||
s.calledWithPodName = append(s.calledWithPodName, podName)
|
||||
s.calledWithCommandAndArgs = append(s.calledWithCommandAndArgs, commandAndArgs)
|
||||
result := s.resultsToReturn[s.callCount]
|
||||
var err error = nil
|
||||
if s.errorsToReturn != nil {
|
||||
err = s.errorsToReturn[s.callCount]
|
||||
}
|
||||
s.callCount++
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type callbackRecorder struct {
|
||||
numberOfTimesSuccessCalled int
|
||||
numberOfTimesFailureCalled int
|
||||
failureErrors []error
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (c *callbackRecorder) OnSuccess() {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.numberOfTimesSuccessCalled++
|
||||
}
|
||||
|
||||
func (c *callbackRecorder) OnFailure(err error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.numberOfTimesFailureCalled++
|
||||
c.failureErrors = append(c.failureErrors, err)
|
||||
}
|
||||
|
||||
func (c *callbackRecorder) NumberOfTimesSuccessCalled() int {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
return c.numberOfTimesSuccessCalled
|
||||
}
|
||||
|
||||
func (c *callbackRecorder) NumberOfTimesFailureCalled() int {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
return c.numberOfTimesFailureCalled
|
||||
}
|
||||
|
||||
func (c *callbackRecorder) FailureErrors() []error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
var errs = make([]error, len(c.failureErrors))
|
||||
copy(errs, c.failureErrors)
|
||||
return errs
|
||||
}
|
||||
|
||||
func TestCA(t *testing.T) {
|
||||
spec.Run(t, "CA", func(t *testing.T, when spec.G, it spec.S) {
|
||||
var r *require.Assertions
|
||||
var fakeCertPEM, fakeKeyPEM string
|
||||
var fakeCert2PEM, fakeKey2PEM string
|
||||
var fakePod *corev1.Pod
|
||||
var kubeAPIClient *kubernetesfake.Clientset
|
||||
var fakeExecutor *fakePodExecutor
|
||||
var neverTicker <-chan time.Time
|
||||
var callbacks *callbackRecorder
|
||||
var logger *testutil.TranscriptLogger
|
||||
var agentInfo AgentInfo
|
||||
|
||||
var requireInitialFailureLogMessage = func(specificErrorMessage string) {
|
||||
r.Len(logger.Transcript(), 1)
|
||||
r.Equal(
|
||||
fmt.Sprintf("could not initially fetch the API server's signing key: %s\n", specificErrorMessage),
|
||||
logger.Transcript()[0].Message,
|
||||
)
|
||||
r.Equal(logger.Transcript()[0].Level, "error")
|
||||
}
|
||||
|
||||
var requireNotCapableOfIssuingCerts = func(subject *CA) {
|
||||
certPEM, keyPEM, err := subject.IssuePEM(
|
||||
pkix.Name{CommonName: "Test Server"},
|
||||
[]string{"example.com"},
|
||||
10*time.Minute,
|
||||
)
|
||||
r.Nil(certPEM)
|
||||
r.Nil(keyPEM)
|
||||
r.EqualError(err, "this cluster is not currently capable of issuing certificates")
|
||||
}
|
||||
|
||||
it.Before(func() {
|
||||
r = require.New(t)
|
||||
|
||||
loadFile := func(filename string) string {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
r.NoError(err)
|
||||
return string(bytes)
|
||||
}
|
||||
fakeCertPEM = loadFile("./testdata/test.crt")
|
||||
fakeKeyPEM = loadFile("./testdata/test.key")
|
||||
fakeCert2PEM = loadFile("./testdata/test2.crt")
|
||||
fakeKey2PEM = loadFile("./testdata/test2.key")
|
||||
|
||||
agentInfo = AgentInfo{
|
||||
Namespace: "some-agent-namespace",
|
||||
LabelSelector: "some-label-key=some-label-value",
|
||||
CertPathAnnotation: "some-cert-path-annotation",
|
||||
KeyPathAnnotation: "some-key-path-annotation",
|
||||
}
|
||||
fakePod = &corev1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "fake-pod",
|
||||
Namespace: agentInfo.Namespace,
|
||||
Labels: map[string]string{
|
||||
"some-label-key": "some-label-value",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{Name: "some-agent-container-name"}},
|
||||
},
|
||||
Status: corev1.PodStatus{
|
||||
Phase: corev1.PodRunning,
|
||||
},
|
||||
}
|
||||
|
||||
kubeAPIClient = kubernetesfake.NewSimpleClientset()
|
||||
|
||||
fakeExecutor = &fakePodExecutor{
|
||||
resultsToReturn: []string{
|
||||
fakeCertPEM,
|
||||
fakeKeyPEM,
|
||||
fakeCert2PEM,
|
||||
fakeKey2PEM,
|
||||
},
|
||||
}
|
||||
|
||||
callbacks = &callbackRecorder{}
|
||||
|
||||
logger = testutil.NewTranscriptLogger(t)
|
||||
klog.SetLogger(logger) // this is unfortunately a global logger, so can't run these tests in parallel :(
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
klog.SetLogger(nil)
|
||||
})
|
||||
|
||||
when("the agent pod is found with default CLI flag values", func() {
|
||||
it.Before(func() {
|
||||
err := kubeAPIClient.Tracker().Add(fakePod)
|
||||
r.NoError(err)
|
||||
})
|
||||
|
||||
when("the exec commands return the API server's keypair", func() {
|
||||
it("finds the API server's signing key and uses it to issue certificates", func() {
|
||||
fakeTicker := make(chan time.Time)
|
||||
|
||||
subject, shutdownFunc := New(&agentInfo, kubeAPIClient, fakeExecutor, fakeTicker, callbacks.OnSuccess, callbacks.OnFailure)
|
||||
defer shutdownFunc()
|
||||
|
||||
r.Equal(2, fakeExecutor.callCount)
|
||||
|
||||
r.Equal(agentInfo.Namespace, fakeExecutor.calledWithPodNamespace[0])
|
||||
r.Equal("fake-pod", fakeExecutor.calledWithPodName[0])
|
||||
r.Equal([]string{"cat", "/etc/kubernetes/ca/ca.pem"}, fakeExecutor.calledWithCommandAndArgs[0])
|
||||
|
||||
r.Equal(agentInfo.Namespace, fakeExecutor.calledWithPodNamespace[1])
|
||||
r.Equal("fake-pod", fakeExecutor.calledWithPodName[1])
|
||||
r.Equal([]string{"cat", "/etc/kubernetes/ca/ca.key"}, fakeExecutor.calledWithCommandAndArgs[1])
|
||||
|
||||
r.Equal(1, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(0, callbacks.NumberOfTimesFailureCalled())
|
||||
|
||||
// Validate that we can issue a certificate signed by the original API server CA.
|
||||
certPEM, keyPEM, err := subject.IssuePEM(
|
||||
pkix.Name{CommonName: "Test Server"},
|
||||
[]string{"example.com"},
|
||||
10*time.Minute,
|
||||
)
|
||||
r.NoError(err)
|
||||
validCert := testutil.ValidateCertificate(t, fakeCertPEM, string(certPEM))
|
||||
validCert.RequireDNSName("example.com")
|
||||
validCert.RequireLifetime(time.Now(), time.Now().Add(10*time.Minute), 6*time.Minute)
|
||||
validCert.RequireMatchesPrivateKey(string(keyPEM))
|
||||
|
||||
// Tick the timer and wait for another refresh loop to complete.
|
||||
fakeTicker <- time.Now()
|
||||
|
||||
// Eventually it starts issuing certs using the new signing key.
|
||||
var secondCertPEM, secondKeyPEM string
|
||||
r.Eventually(func() bool {
|
||||
certPEM, keyPEM, err := subject.IssuePEM(
|
||||
pkix.Name{CommonName: "Test Server"},
|
||||
[]string{"example.com"},
|
||||
10*time.Minute,
|
||||
)
|
||||
r.NoError(err)
|
||||
secondCertPEM = string(certPEM)
|
||||
secondKeyPEM = string(keyPEM)
|
||||
|
||||
block, _ := pem.Decode(certPEM)
|
||||
require.NotNil(t, block)
|
||||
parsed, err := x509.ParseCertificate(block.Bytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Validate the created cert using the second API server CA.
|
||||
roots := x509.NewCertPool()
|
||||
require.True(t, roots.AppendCertsFromPEM([]byte(fakeCert2PEM)))
|
||||
opts := x509.VerifyOptions{Roots: roots}
|
||||
_, err = parsed.Verify(opts)
|
||||
return err == nil
|
||||
}, 5*time.Second, 100*time.Millisecond)
|
||||
|
||||
r.Equal(2, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(0, callbacks.NumberOfTimesFailureCalled())
|
||||
|
||||
validCert2 := testutil.ValidateCertificate(t, fakeCert2PEM, secondCertPEM)
|
||||
validCert2.RequireDNSName("example.com")
|
||||
validCert2.RequireLifetime(time.Now(), time.Now().Add(15*time.Minute), 6*time.Minute)
|
||||
validCert2.RequireMatchesPrivateKey(secondKeyPEM)
|
||||
})
|
||||
})
|
||||
|
||||
when("the exec commands return the API server's keypair the first time but subsequently fails", func() {
|
||||
it.Before(func() {
|
||||
fakeExecutor.errorsToReturn = []error{nil, nil, fmt.Errorf("some exec error")}
|
||||
})
|
||||
|
||||
it("logs an error message", func() {
|
||||
fakeTicker := make(chan time.Time)
|
||||
|
||||
subject, shutdownFunc := New(&agentInfo, kubeAPIClient, fakeExecutor, fakeTicker, callbacks.OnSuccess, callbacks.OnFailure)
|
||||
defer shutdownFunc()
|
||||
r.Equal(2, fakeExecutor.callCount)
|
||||
r.Equal(1, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(0, callbacks.NumberOfTimesFailureCalled())
|
||||
|
||||
// Tick the timer and wait for another refresh loop to complete.
|
||||
fakeTicker <- time.Now()
|
||||
|
||||
// Wait for there to be a log output and require that it matches our expectation.
|
||||
r.Eventually(func() bool { return len(logger.Transcript()) >= 1 }, 5*time.Second, 10*time.Millisecond)
|
||||
r.Contains(logger.Transcript()[0].Message, "could not create signer with API server secret: some exec error")
|
||||
r.Equal(logger.Transcript()[0].Level, "error")
|
||||
|
||||
r.Equal(1, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(1, callbacks.NumberOfTimesFailureCalled())
|
||||
r.EqualError(callbacks.FailureErrors()[0], "some exec error")
|
||||
|
||||
// Validate that we can still issue a certificate signed by the original API server CA.
|
||||
certPEM, _, err := subject.IssuePEM(
|
||||
pkix.Name{CommonName: "Test Server"},
|
||||
[]string{"example.com"},
|
||||
10*time.Minute,
|
||||
)
|
||||
r.NoError(err)
|
||||
testutil.ValidateCertificate(t, fakeCertPEM, string(certPEM))
|
||||
})
|
||||
})
|
||||
|
||||
when("the exec commands fail the first time but subsequently returns the API server's keypair", func() {
|
||||
it.Before(func() {
|
||||
fakeExecutor.errorsToReturn = []error{fmt.Errorf("some exec error"), nil, nil}
|
||||
fakeExecutor.resultsToReturn = []string{"", fakeCertPEM, fakeKeyPEM}
|
||||
})
|
||||
|
||||
it("logs an error message and fails to issue certs until it can get the API server's keypair", func() {
|
||||
fakeTicker := make(chan time.Time)
|
||||
|
||||
subject, shutdownFunc := New(&agentInfo, kubeAPIClient, fakeExecutor, fakeTicker, callbacks.OnSuccess, callbacks.OnFailure)
|
||||
defer shutdownFunc()
|
||||
r.Equal(1, fakeExecutor.callCount)
|
||||
r.Equal(0, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(1, callbacks.NumberOfTimesFailureCalled())
|
||||
r.EqualError(callbacks.FailureErrors()[0], "some exec error")
|
||||
|
||||
requireInitialFailureLogMessage("some exec error")
|
||||
requireNotCapableOfIssuingCerts(subject)
|
||||
|
||||
// Tick the timer and wait for another refresh loop to complete.
|
||||
fakeTicker <- time.Now()
|
||||
|
||||
// Wait until it can start to issue certs, and then validate the issued cert.
|
||||
var certPEM, keyPEM []byte
|
||||
r.Eventually(func() bool {
|
||||
var err error
|
||||
certPEM, keyPEM, err = subject.IssuePEM(
|
||||
pkix.Name{CommonName: "Test Server"},
|
||||
[]string{"example.com"},
|
||||
10*time.Minute,
|
||||
)
|
||||
return err == nil
|
||||
}, 5*time.Second, 10*time.Millisecond)
|
||||
validCert := testutil.ValidateCertificate(t, fakeCertPEM, string(certPEM))
|
||||
validCert.RequireDNSName("example.com")
|
||||
validCert.RequireLifetime(time.Now().Add(-5*time.Minute), time.Now().Add(10*time.Minute), 1*time.Minute)
|
||||
validCert.RequireMatchesPrivateKey(string(keyPEM))
|
||||
|
||||
r.Equal(1, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(1, callbacks.NumberOfTimesFailureCalled())
|
||||
})
|
||||
})
|
||||
|
||||
when("the exec commands succeed but return garbage", func() {
|
||||
it.Before(func() {
|
||||
fakeExecutor.resultsToReturn = []string{"not a cert", "not a private key"}
|
||||
})
|
||||
|
||||
it("returns a CA who cannot issue certs", func() {
|
||||
subject, shutdownFunc := New(&agentInfo, kubeAPIClient, fakeExecutor, neverTicker, callbacks.OnSuccess, callbacks.OnFailure)
|
||||
defer shutdownFunc()
|
||||
requireInitialFailureLogMessage("could not load CA: tls: failed to find any PEM data in certificate input")
|
||||
requireNotCapableOfIssuingCerts(subject)
|
||||
r.Equal(0, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(1, callbacks.NumberOfTimesFailureCalled())
|
||||
r.EqualError(callbacks.FailureErrors()[0], "could not load CA: tls: failed to find any PEM data in certificate input")
|
||||
})
|
||||
})
|
||||
|
||||
when("the first exec command returns an error", func() {
|
||||
it.Before(func() {
|
||||
fakeExecutor.errorsToReturn = []error{fmt.Errorf("some error"), nil}
|
||||
})
|
||||
|
||||
it("returns a CA who cannot issue certs", func() {
|
||||
subject, shutdownFunc := New(&agentInfo, kubeAPIClient, fakeExecutor, neverTicker, callbacks.OnSuccess, callbacks.OnFailure)
|
||||
defer shutdownFunc()
|
||||
requireInitialFailureLogMessage("some error")
|
||||
requireNotCapableOfIssuingCerts(subject)
|
||||
r.Equal(0, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(1, callbacks.NumberOfTimesFailureCalled())
|
||||
r.EqualError(callbacks.FailureErrors()[0], "some error")
|
||||
})
|
||||
})
|
||||
|
||||
when("the second exec command returns an error", func() {
|
||||
it.Before(func() {
|
||||
fakeExecutor.errorsToReturn = []error{nil, fmt.Errorf("some error")}
|
||||
})
|
||||
|
||||
it("returns a CA who cannot issue certs", func() {
|
||||
subject, shutdownFunc := New(&agentInfo, kubeAPIClient, fakeExecutor, neverTicker, callbacks.OnSuccess, callbacks.OnFailure)
|
||||
defer shutdownFunc()
|
||||
requireInitialFailureLogMessage("some error")
|
||||
requireNotCapableOfIssuingCerts(subject)
|
||||
r.Equal(0, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(1, callbacks.NumberOfTimesFailureCalled())
|
||||
r.EqualError(callbacks.FailureErrors()[0], "some error")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
when("the agent pod is found with non-default CLI flag values", func() {
|
||||
it.Before(func() {
|
||||
fakePod.Annotations = make(map[string]string)
|
||||
fakePod.Annotations[agentInfo.CertPathAnnotation] = "/etc/kubernetes/ca/non-default.pem"
|
||||
fakePod.Annotations[agentInfo.KeyPathAnnotation] = "/etc/kubernetes/ca/non-default.key"
|
||||
err := kubeAPIClient.Tracker().Add(fakePod)
|
||||
r.NoError(err)
|
||||
})
|
||||
|
||||
it("finds the API server's signing key and uses it to issue certificates", func() {
|
||||
_, shutdownFunc := New(&agentInfo, kubeAPIClient, fakeExecutor, neverTicker, callbacks.OnSuccess, callbacks.OnFailure)
|
||||
defer shutdownFunc()
|
||||
|
||||
r.Equal(2, fakeExecutor.callCount)
|
||||
|
||||
r.Equal(agentInfo.Namespace, fakeExecutor.calledWithPodNamespace[0])
|
||||
r.Equal("fake-pod", fakeExecutor.calledWithPodName[0])
|
||||
r.Equal([]string{"cat", "/etc/kubernetes/ca/non-default.pem"}, fakeExecutor.calledWithCommandAndArgs[0])
|
||||
|
||||
r.Equal(agentInfo.Namespace, fakeExecutor.calledWithPodNamespace[1])
|
||||
r.Equal("fake-pod", fakeExecutor.calledWithPodName[1])
|
||||
r.Equal([]string{"cat", "/etc/kubernetes/ca/non-default.key"}, fakeExecutor.calledWithCommandAndArgs[1])
|
||||
})
|
||||
})
|
||||
|
||||
when("the agent pod is not found", func() {
|
||||
it("returns an error", func() {
|
||||
subject, shutdownFunc := New(&agentInfo, kubeAPIClient, fakeExecutor, neverTicker, callbacks.OnSuccess, callbacks.OnFailure)
|
||||
defer shutdownFunc()
|
||||
requireInitialFailureLogMessage("did not find kube-cert-agent pod")
|
||||
requireNotCapableOfIssuingCerts(subject)
|
||||
r.Equal(0, callbacks.NumberOfTimesSuccessCalled())
|
||||
r.Equal(1, callbacks.NumberOfTimesFailureCalled())
|
||||
r.EqualError(callbacks.FailureErrors()[0], "did not find kube-cert-agent pod")
|
||||
})
|
||||
})
|
||||
}, spec.Sequential(), spec.Report(report.Terminal{}))
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
|
||||
cm5ldGVzMB4XDTIwMDcyNTIxMDQxOFoXDTMwMDcyMzIxMDQxOFowFTETMBEGA1UE
|
||||
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3K
|
||||
hYv2gIQ1Dwzh2cWMid+ofAnvLIfV2Xv61vTLGprUI+XUqB4/gtf6X6UNn0Lett2n
|
||||
d8p4wy7hw73hU/ggdvmWJvqBrSjc3JGfy+kj66fKXX+PTlbL7QbwiRvcSqIXIWlV
|
||||
lHHxECWrED8jCulw/NVqfook/h5iNUCT9yswSJr/0fImiVnoTlIoEYG2eCNejZ5c
|
||||
g39uD3ZTqd9ZxWwSLLnI+2kpJnZBPcd1ZQ8AQqzDgZtYRCqacn5gckQUKZWKQlxo
|
||||
Eft6g1XHJouAWAZw7hEtk0v8rG0/eKF7wamxFi6BFVlbjWBsB4T9rApbdBWTKeCJ
|
||||
Hv8fv5RMFSzpT3uzTO8CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACh5RhbxqJe+Z/gc17cZhKNmdiwu
|
||||
I2pLp3QBfwvN+Wbmajzw/7rYhY0d8JYVTJzXSCPWi6UAKxAtXOLF8WIIf9i39n6R
|
||||
uKOBGW14FzzGyRJiD3qaG/JTvEW+SLhwl68Ndr5LHSnbugAqq31abcQy6Zl9v5A8
|
||||
JKC97Lj/Sn8rj7opKy4W3oq7NCQsAb0zh4IllRF6UvSnJySfsg7xdXHHpxYDHtOS
|
||||
XcOu5ySUIZTgFe9RfeUZlGZ5xn0ckMlQ7qW2Wx1q0OVWw5us4NtkGqKrHG4Tn1X7
|
||||
uwo/Yytn5sDxrDv1/oii6AZOCsTPre4oD3wz4nmVzCVJcgrqH4Q24hT8WNg=
|
||||
-----END CERTIFICATE-----
|
@ -1,27 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAvcqFi/aAhDUPDOHZxYyJ36h8Ce8sh9XZe/rW9MsamtQj5dSo
|
||||
Hj+C1/pfpQ2fQt623ad3ynjDLuHDveFT+CB2+ZYm+oGtKNzckZ/L6SPrp8pdf49O
|
||||
VsvtBvCJG9xKohchaVWUcfEQJasQPyMK6XD81Wp+iiT+HmI1QJP3KzBImv/R8iaJ
|
||||
WehOUigRgbZ4I16NnlyDf24PdlOp31nFbBIsucj7aSkmdkE9x3VlDwBCrMOBm1hE
|
||||
KppyfmByRBQplYpCXGgR+3qDVccmi4BYBnDuES2TS/ysbT94oXvBqbEWLoEVWVuN
|
||||
YGwHhP2sClt0FZMp4Ike/x+/lEwVLOlPe7NM7wIDAQABAoIBAFC1tUEmHNUcM0BJ
|
||||
M3D9KQzB+63F1mwVlx1QOOV1EeVR3co5Ox1R6PSr9sycFGQ9jgqI0zp5TJe9Tp6L
|
||||
GkhklfPh1MWnK9o6wlnzWKXWrrp2Jni+mpPyuOPAmq4Maniv2XeP+0bROwqpyojv
|
||||
AA7yC7M+TH226ZJGNVs3EV9+cwHml0yuzBfIJn/rv/w2g+WRKM/MC0S7k2d8bRlA
|
||||
NycKVGAGBhKTltjoVYOeh6aHEpSjK8zfaePjo5dYJvoVIli60YCgcJOU/8jXT+Np
|
||||
1Fm7tRvAtj3pUp0Sqdaf2RUzh9jfJp2VFCHuSJ6TPqArOyQojtMcTHF0TiW7xrHP
|
||||
xOCRIAECgYEAwGBPU7vdthMJBg+ORUoGQQaItTeJvQwIqJvbKD2osp4jhS1dGZBw
|
||||
W30GKEc/gd8JNtOq9BBnMicPF7hktuy+bSPv41XPud67rSSO7Tsw20C10gFRq06B
|
||||
zIJWFAUqK3IkvVc3VDmtSLSDox4QZ/BdqaMlQ5y5JCsC5kThmkZFlO8CgYEA/I9X
|
||||
YHi6RioMJE1fqOHJL4DDjlezmcuRrD7fE5InKbtJZ2JhGYOX/C0KXnHTOWTCDxxN
|
||||
FBvpvD6Xv5o3PhB9Z6k2fqvJ4GS8urkG/KU4xcC+bak+9ava8oaiSqG16zD9NH2P
|
||||
jJ60NrbLl1J0pU9fiwuFVUKJ4hDZOfN9RqYdyAECgYAVwo8WhJiGgM6zfcz073OX
|
||||
pVqPTPHqjVLpZ3+5pIfRdGvGI6R1QM5EuvaYVb7MPOM47WZX5wcVOC/P2g6iVlMP
|
||||
21HGIC2384a9BfaYxOo40q/+SiHnw6CQ9mkwKIllkqqvNA9RGpkMMUb2i28For2l
|
||||
c4vCgxa6DZdtXns6TRqPxwKBgCfY5cxOv/T6BVhk7MbUeM2J31DB/ZAyUhV/Bess
|
||||
kAlBh19MYk2IOZ6L7KriApV3lDaWHIMjtEkDByYvyq98Io0MYZCywfMpca10K+oI
|
||||
l2B7/I+IuGpCZxUEsO5dfTpSTGDPvqpND9niFVUWqVi7oTNq6ep9yQtl5SADjqxq
|
||||
4SABAoGAIm0hUg1wtcS46cGLy6PIkPM5tocTSghtz4vFsuk/i4QA9GBoBO2gH6ty
|
||||
+kJHmeaXt2dmgySp0QAWit5UlceEumB0NXnAdJZQxeGSFSyYkDWhwXd8wDceKo/1
|
||||
LfCU6Dk8IN/SsppVUWXQ2rlORvxlrHeCio8o0kS9Yiu55WMYg4g=
|
||||
-----END RSA PRIVATE KEY-----
|
@ -1,17 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
|
||||
cm5ldGVzMB4XDTIwMDgxODE2NDEzNloXDTMwMDgxNjE2NDEzNlowFTETMBEGA1UE
|
||||
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALH7
|
||||
C2JpttDi3mxpD4bd+BZucCrS8XF2YwqYAr42HePp++PBnlUFqWmtPc9/bmo+7+7z
|
||||
iAAlnAV0pJWP+HR/PskX8MRcFAA1HoXLa37Q4SuBBQG+JE+AeaOObmQYaCFv55ej
|
||||
UF4+JIoQOdlbYEMYSI07el0cxQL4Io/CHJ3p7AtNElxjDuMK4B9W8NiCse3p7Uf+
|
||||
Qje4we1TYOfcpAM0jpBPHK9vCBCpX+j52S5DUTRVIk9kye3lCDmWOXH/fhj/aJTM
|
||||
1MP/hThbl2wIbFuv1bpa0kXNZs8xB63dtqROQ+lCghDmuayRmzwXl2PX6IgFFcjV
|
||||
yAgjXrZqjihs+mY8eT0CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAE+Saqk2EyuIx1rxFWrOwpTi5q/B
|
||||
p/TwEtrmrFIRVPnGeBnhyfbGXPDMkzIY1mEvztu8H+pm5RPyhQYLsuwzYiYMQyxX
|
||||
yL9VvO7uydn7+3zX7oknQ5qAvN3nmItNyOKw3MRIKGsySNuTQ5JPtU/ufGlEivbK
|
||||
vNaDBqjKrBvwhIKMdV9/xYSyeBhSSWr/6W1tAk+XbHhQH1M78rdwGN5SI75L4FGu
|
||||
13kn/W2n8pE17TAY88B1YGKhsLSvf8KrFNYv+UUmzh2WstECKSlnbrSM+boMlGJn
|
||||
XahE8M23fieB+SaenQdOezrY4GAnXQ3qToDlhdYAOkWhcGDct47VRM93whY=
|
||||
-----END CERTIFICATE-----
|
@ -1,27 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAsfsLYmm20OLebGkPht34Fm5wKtLxcXZjCpgCvjYd4+n748Ge
|
||||
VQWpaa09z39uaj7v7vOIACWcBXSklY/4dH8+yRfwxFwUADUehctrftDhK4EFAb4k
|
||||
T4B5o45uZBhoIW/nl6NQXj4kihA52VtgQxhIjTt6XRzFAvgij8IcnensC00SXGMO
|
||||
4wrgH1bw2IKx7entR/5CN7jB7VNg59ykAzSOkE8cr28IEKlf6PnZLkNRNFUiT2TJ
|
||||
7eUIOZY5cf9+GP9olMzUw/+FOFuXbAhsW6/VulrSRc1mzzEHrd22pE5D6UKCEOa5
|
||||
rJGbPBeXY9foiAUVyNXICCNetmqOKGz6Zjx5PQIDAQABAoIBAD06klYO7De8dKxz
|
||||
EEZjgn+lCq2Q2EMiaTwxw2/QikPoMSHPcDrrsbaLROJngoLGmCBqY3U5ew1dbWmO
|
||||
l/jr9ZuUwt2ql67il1eL/bUpAu3GewR4d2FqX25nB48j3l7ycof2RSXG1ycwIdam
|
||||
2tz6M6tytMvno9c7qhguvU2ONghEreXG3YYLdf9l97aB+p6GdXhwty22b7tAVwp1
|
||||
GKn79kVYgmL86lph9hBPqtHuG1LHZUiFodr2iWXSu3H/265OD58a33ZO3iyfFI0s
|
||||
PPy87ZN0r+1hGpoKKkDe63udOYgAG6xmIea/1Pdn9Eg87tueoeC7XcUpdaCJlKaF
|
||||
tqCusEECgYEA60rWyXxTFKJ4QdVaqXoWMA4cQkT73RxznSKwkN/Svk8TVv+p5s5Y
|
||||
oYKN4qyMzxvQzu+QNWpd1yTveCmmEynz457ELpGtidtiJdm7xZMdMGrU02eCL9mZ
|
||||
ERbtfAkbEAKvN8D73fWyzghKv4dgcQptmsqZlYYc4vpwHveK+/N5lukCgYEAwaT3
|
||||
iMTWCv7Vp87iKrzNUAH4iBWlazwbE+EDEnHVw26Y82fhgEgxiU2UvFSaIVhGpaCz
|
||||
MYSXSdRcQTHgCoJLPfWHUHTJPqf36KfAJfdaxxjzTTbNYjUOkdcUD1bcNrm0yjoY
|
||||
nR4zK1FPw86ODMYtBpfkyL7ZX8G1v5pRL/6/gzUCgYBzgwQ7Wmu3H6QGPeYKecNW
|
||||
yDabWh6ECKnBpPwlw5xEjbGi7lTM2NSuRde+RpPCQZebYATeFGAJdTqTNW8wzVHM
|
||||
l28cpawal7dxeZkzf+u+j1P4jUJel2cL+sOQNzAwBgFbT8TWzP6BI5T+vklcdZAl
|
||||
g/0uaO7Zh7Vvnnt/AaLZsQKBgGfbHzuGPjoFdPecOKatPfxUIkRyP5bk1KzzuF8T
|
||||
GI/JaFTbeREBJzg5mLTtNwD9RF6ecpzzPOTG9Xet1Tgtq0cewSUAjdKB6a8pESAL
|
||||
qu8vTYYzBzJNvHOxg7u6XT8omHMBd6QEx3LLGFmvFXZ6bzmjC3wzB4iY7u5FSJfS
|
||||
LEqlAoGAb0rbJ85vrJopbx8lzhJjyaJfM8+A3oQg1K3TphHrcgkUc8qx8QEosziM
|
||||
wzYKSBlXd2bxMibyd0mTEMNl4/BqofaKoqof9gBIbkamwXOO8s7IgKxQAfr1R/z8
|
||||
tHBW/g0QWPB+qtaVDtHwyQLlxjx8HD7atIo8d/do9ruwVaf+r6g=
|
||||
-----END RSA PRIVATE KEY-----
|
Loading…
Reference in New Issue
Block a user