-
Notifications
You must be signed in to change notification settings - Fork 0
/
authority.go
125 lines (117 loc) · 3.66 KB
/
authority.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package authority
import (
"context"
"time"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/trace"
"github.com/pborman/uuid"
log "github.com/sirupsen/logrus"
certificates "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
watch "k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
)
// ProcessCSR processes CSR request with local k8s certificate authority
// and returns certificate PEM signed by CA
func ProcessCSR(clt *kubernetes.Clientset, csrPEM []byte) ([]byte, error) {
id := "teleport-" + uuid.New()
requests := clt.CertificatesV1beta1().CertificateSigningRequests()
csr, err := requests.Create(&certificates.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: id,
},
Spec: certificates.CertificateSigningRequestSpec{
Request: csrPEM,
Usages: []certificates.KeyUsage{
certificates.UsageDigitalSignature,
certificates.UsageKeyEncipherment,
certificates.UsageClientAuth,
},
},
})
if err != nil {
return nil, trace.Wrap(err)
}
// Delete CSR as it seems to be hanging forever if not deleted manually.
defer func() {
if err := requests.Delete(id, &metav1.DeleteOptions{}); err != nil {
log.Warningf("Failed to delete CSR: %v.", err)
}
}()
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateApproved,
Reason: "TeleportApprove",
Message: "This CSR was approved by Teleport.",
LastUpdateTime: metav1.Now(),
})
result, err := requests.UpdateApproval(csr)
if err != nil {
return nil, trace.Wrap(err)
}
if result.Status.Certificate != nil {
log.Debugf("Received certificate right after approval, returning.")
return result.Status.Certificate, nil
}
ctx, cancel := context.WithTimeout(context.TODO(), defaults.CSRSignTimeout)
defer cancel()
watchForCert := func() ([]byte, error) {
watcher, err := requests.Watch(metav1.ListOptions{
TypeMeta: metav1.TypeMeta{
Kind: teleport.KubeKindCSR,
},
FieldSelector: fields.Set{teleport.KubeMetadataNameSelector: id}.String(),
Watch: true,
})
if err != nil {
return nil, trace.Wrap(err)
}
cert, err := waitForCSR(ctx, watcher.ResultChan())
watcher.Stop()
if err == nil {
return cert, nil
}
return nil, trace.Wrap(err)
}
// this could be infinite loop, but is limited to certain number
// of iterations just in case.
for i := 0; i < int(defaults.CSRSignTimeout/time.Second); i++ {
cert, err := watchForCert()
if err == nil {
return cert, nil
}
if !trace.IsRetryError(err) {
return nil, trace.Wrap(err)
}
select {
case <-time.After(time.Second):
log.Debugf("Retry after network error: %v.", err)
case <-ctx.Done():
return nil, trace.BadParameter(timeoutCSRMessage)
}
}
return nil, trace.BadParameter(timeoutCSRMessage)
}
const timeoutCSRMessage = "timeout while waiting for Kubernetes certificate"
func waitForCSR(ctx context.Context, eventsC <-chan watch.Event) ([]byte, error) {
for {
select {
case event, ok := <-eventsC:
if !ok {
return nil, trace.Retry(nil, "events channel closed")
}
csr, ok := event.Object.(*certificates.CertificateSigningRequest)
if !ok {
log.Warnf("Unexpected resource type: %T, expected %T.", event.Object, &certificates.CertificateSigningRequest{})
continue
}
if csr.Status.Certificate != nil {
return csr.Status.Certificate, nil
}
log.Debugf("CSR got updated, but certificate is not ready yet: %v.", csr.Status.Conditions)
case <-ctx.Done():
return nil, trace.BadParameter(timeoutCSRMessage)
}
}
}