forked from cert-manager/cert-manager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
setup.go
138 lines (122 loc) · 4.81 KB
/
setup.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
126
127
128
129
130
131
132
133
134
135
136
137
138
package acme
import (
"context"
"crypto/rsa"
"fmt"
"net/http"
"strings"
"github.com/golang/glog"
"golang.org/x/crypto/acme"
"k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/pkg/util/errors"
"github.com/jetstack/cert-manager/pkg/util/kube"
"github.com/jetstack/cert-manager/pkg/util/pki"
)
const (
errorAccountRegistrationFailed = "ErrRegisterACMEAccount"
errorAccountVerificationFailed = "ErrVerifyACMEAccount"
successAccountRegistered = "ACMEAccountRegistered"
successAccountVerified = "ACMEAccountVerified"
messageAccountRegistrationFailed = "Failed to register ACME account: "
messageAccountVerificationFailed = "Failed to verify ACME account: "
messageAccountRegistered = "The ACME account was registered with the ACME server"
messageAccountVerified = "The ACME account was verified with the ACME server"
)
// Setup will verify an existing ACME registration, or create one if not
// already registered.
func (a *Acme) Setup(ctx context.Context) error {
cl, err := a.acmeClient()
if k8sErrors.IsNotFound(err) || errors.IsInvalidData(err) {
glog.V(4).Infof("%s: generating acme account private key %q", a.issuer.GetObjectMeta().Name, a.issuer.GetSpec().ACME.PrivateKey.Name)
accountPrivKey, err := a.createAccountPrivateKey()
if err != nil {
s := messageAccountRegistrationFailed + err.Error()
a.issuer.UpdateStatusCondition(v1alpha1.IssuerConditionReady, v1alpha1.ConditionFalse, errorAccountRegistrationFailed, s)
return fmt.Errorf(s)
}
a.issuer.GetStatus().ACMEStatus().URI = ""
cl = &acme.Client{
Key: accountPrivKey,
DirectoryURL: a.issuer.GetSpec().ACME.Server,
}
} else if err != nil {
s := messageAccountVerificationFailed + err.Error()
glog.V(4).Infof("%s: %s", a.issuer.GetObjectMeta().Name, s)
a.recorder.Event(a.issuer, v1.EventTypeWarning, errorAccountVerificationFailed, s)
return err
}
account, err := a.registerAccount(ctx, cl)
if err != nil {
s := messageAccountVerificationFailed + err.Error()
glog.V(4).Infof("%s: %s", a.issuer.GetObjectMeta().Name, s)
a.recorder.Event(a.issuer, v1.EventTypeWarning, errorAccountVerificationFailed, s)
a.issuer.UpdateStatusCondition(v1alpha1.IssuerConditionReady, v1alpha1.ConditionFalse, errorAccountRegistrationFailed, s)
return err
}
glog.V(4).Infof("%s: verified existing registration with ACME server", a.issuer.GetObjectMeta().Name)
a.issuer.UpdateStatusCondition(v1alpha1.IssuerConditionReady, v1alpha1.ConditionTrue, successAccountRegistered, messageAccountRegistered)
a.issuer.GetStatus().ACMEStatus().URI = account.URI
return nil
}
// registerAccount will register a new ACME account with the server. If an
// account with the clients private key already exists, it will attempt to look
// up and verify the corresponding account, and will return that. If this fails
// it will return the
func (a *Acme) registerAccount(ctx context.Context, cl *acme.Client) (*acme.Account, error) {
acc := &acme.Account{
Contact: []string{fmt.Sprintf("mailto:%s", strings.ToLower(a.issuer.GetSpec().ACME.Email))},
}
acc, err := cl.Register(ctx, acc, acme.AcceptTOS)
if err != nil {
typedErr, ok := err.(*acme.Error)
// if this isn't an ACME error, we should just return it
if !ok {
return nil, err
}
// StatusConflict means an account with the users private key already exists.
// If the response code was *not* StatusConflict, we should return the error
// here as we are not able to handle it.
if typedErr.StatusCode != http.StatusConflict {
return nil, err
}
// If StatusConflict was the returned error, we can attempt to look up the existing
// registration URI in the response headers.
accountUri := typedErr.Header.Get("Location")
if accountUri == "" {
return nil, fmt.Errorf("unexpected error - 409 Conflict error returned, but no Location header set: %s", typedErr.Error())
}
return a.verifyAccount(ctx, cl, accountUri)
}
return acc, nil
}
// verifyAccount will verify an ACME account with the given URI.
func (a *Acme) verifyAccount(ctx context.Context, cl *acme.Client, uri string) (*acme.Account, error) {
acc, err := cl.GetReg(ctx, uri)
if err != nil {
return nil, err
}
return acc, nil
}
func (a *Acme) createAccountPrivateKey() (*rsa.PrivateKey, error) {
secretName, secretKey := a.acmeAccountPrivateKeyMeta()
accountPrivKey, err := pki.GenerateRSAPrivateKey(2048)
if err != nil {
return nil, err
}
_, err = kube.EnsureSecret(a.client, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: a.issuerResourcesNamespace,
},
Data: map[string][]byte{
secretKey: pki.EncodePKCS1PrivateKey(accountPrivKey),
},
})
if err != nil {
return nil, err
}
return accountPrivKey, err
}