-
Notifications
You must be signed in to change notification settings - Fork 1
/
certificates.go
142 lines (125 loc) · 4.03 KB
/
certificates.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
139
140
141
142
package operations
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"path"
"strings"
"text/template"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/vault/api"
)
// IssueCertificateRequest is the structure containing
// the required data to issue a new certificate
type IssueCertificateRequest struct {
Client *api.Client
VaultPKIPaths []string
Username string
VaultPKIRole string
ClientVPNEndpointID string
VaultKVPath string
CfgTplPath string
Temporary bool
}
// IssueClientCertificate generates a new certificate for a given users, causing
// the revocation of other certificates emitted for that same user
func IssueClientCertificate(r *IssueCertificateRequest) (string, error) {
// Init the struct to pass to the config.ovpn.tpl template
data := struct {
DNSName string
Username string
CA string
Certificate string
PrivateKey string
}{
Username: r.Username,
}
// Issue a new certificate
payload := make(map[string]interface{})
payload["common_name"] = r.Username
crt, err := r.Client.Logical().Write(fmt.Sprintf("%s/issue/%s", r.VaultPKIPaths[len(r.VaultPKIPaths)-1], r.VaultPKIRole), payload)
if err != nil {
return "", err
}
data.Certificate = crt.Data["certificate"].(string)
data.PrivateKey = crt.Data["private_key"].(string)
log.Printf("Issued certificate %s", crt.Data["serial_number"])
// Get the full CA chain of certificates from Vault
// (the VPN config needs the full CA chain to the root CA in it)
var caCerts []string
for _, path := range r.VaultPKIPaths {
req := r.Client.NewRequest("GET", fmt.Sprintf("/v1/%s/ca/pem", path))
raw, err := r.Client.RawRequest(req)
if err != nil {
return "", err
}
defer raw.Body.Close()
ca, err := ioutil.ReadAll(raw.Body)
if err != nil {
return "", err
}
caCerts = append(caCerts, string(ca))
}
data.CA = strings.Join(caCerts, "\n")
// Get the VPN's DNS name from EC2 API
svc := ec2.New(session.New())
rsp, err := svc.DescribeClientVpnEndpoints(
&ec2.DescribeClientVpnEndpointsInput{ClientVpnEndpointIds: aws.StringSlice([]string{r.ClientVPNEndpointID})})
if err != nil {
return "", err
}
// AWS returns the DNSName with an asterisk at the beginning, meaning that any subdomain
// of the VPN's endpoint domain is valid. We need to strip this from the dns to use it
// in the config
data.DNSName = strings.SplitN(*rsp.ClientVpnEndpoints[0].DnsName, ".", 2)[1]
// Resolve the config.ovpn.tpl template
tpl, err := template.New(path.Base(r.CfgTplPath)).ParseFiles(r.CfgTplPath)
if err != nil {
return "", err
}
var config bytes.Buffer
if err := tpl.Execute(&config, data); err != nil {
return "", err
}
if !r.Temporary {
// create/update the vpn config in the kv store
payload["data"] = map[string]string{
"content": config.String(),
}
_, err = r.Client.Logical().Write(fmt.Sprintf("%s/data/users/%s/config.ovpn", r.VaultKVPath, r.Username), payload)
if err != nil {
return "", err
}
// Call UpdateCRL to revoke all other certificates
_, err = UpdateCRL(
&UpdateCRLRequest{
Client: r.Client,
VaultPKIPath: r.VaultPKIPaths[len(r.VaultPKIPaths)-1],
ClientVPNEndpointID: r.ClientVPNEndpointID,
})
if err != nil {
return "", err
}
}
return config.String(), nil
}
// revokeUserCertificates receives a list of certificates, sorted from oldest to newest, and revokes
// all but the latest if "revokeAll" is false and all of them if "revokeAll" is true.
func revokeUserCertificates(client *api.Client, pki string, crts []Certificate, revokeAll bool) error {
for n, crt := range crts {
// Do not revoke the last certificate
if n == len(crts)-1 && revokeAll == false {
break
}
if crt.Revoked == false {
payload := make(map[string]interface{})
payload["serial_number"] = crt.SerialNumber
log.Printf("Revoked cert %s\n", crt.SerialNumber)
client.Logical().Write(fmt.Sprintf("%s/revoke", pki), payload)
}
}
return nil
}