-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
path_issue.go
183 lines (158 loc) · 5.13 KB
/
path_issue.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package ssh
import (
"context"
"crypto/rand"
"errors"
"fmt"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
type keySpecs struct {
Type string
Bits int
}
func pathIssue(b *backend) *framework.Path {
return &framework.Path{
Pattern: "issue/" + framework.GenericNameWithAtRegex("role"),
Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathIssue,
},
},
Fields: map[string]*framework.FieldSchema{
"role": {
Type: framework.TypeString,
Description: `The desired role with configuration for this request.`,
},
"key_type": {
Type: framework.TypeString,
Description: "Specifies the desired key type; must be `rsa`, `ed25519` or `ec`",
Default: "rsa",
},
"key_bits": {
Type: framework.TypeInt,
Description: "Specifies the number of bits to use for the generated keys.",
Default: 0,
},
"ttl": {
Type: framework.TypeDurationSecond,
Description: `The requested Time To Live for the SSH certificate;
sets the expiration date. If not specified
the role default, backend default, or system
default TTL is used, in that order. Cannot
be later than the role max TTL.`,
},
"valid_principals": {
Type: framework.TypeString,
Description: `Valid principals, either usernames or hostnames, that the certificate should be signed for.`,
},
"cert_type": {
Type: framework.TypeString,
Description: `Type of certificate to be created; either "user" or "host".`,
Default: "user",
},
"key_id": {
Type: framework.TypeString,
Description: `Key id that the created certificate should have. If not specified, the display name of the token will be used.`,
},
"critical_options": {
Type: framework.TypeMap,
Description: `Critical options that the certificate should be signed for.`,
},
"extensions": {
Type: framework.TypeMap,
Description: `Extensions that the certificate should be signed for.`,
},
},
HelpSynopsis: pathIssueHelpSyn,
HelpDescription: pathIssueHelpDesc,
}
}
func (b *backend) pathIssue(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Get the role
roleName := data.Get("role").(string)
role, err := b.getRole(ctx, req.Storage, roleName)
if err != nil {
return nil, err
}
if role == nil {
return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", roleName)), nil
}
if role.KeyType != "ca" {
return logical.ErrorResponse("role key type '%s' not allowed to issue key pairs", role.KeyType), nil
}
// Validate and extract key specifications
keySpecs, err := extractKeySpecs(role, data)
if err != nil {
return logical.ErrorResponse(err.Error()), nil
}
// Issue certificate
return b.pathIssueCertificate(ctx, req, data, role, keySpecs)
}
func (b *backend) pathIssueCertificate(ctx context.Context, req *logical.Request, data *framework.FieldData, role *sshRole, keySpecs *keySpecs) (*logical.Response, error) {
publicKey, privateKey, err := generateSSHKeyPair(rand.Reader, keySpecs.Type, keySpecs.Bits)
if err != nil {
return nil, err
}
// Sign key
userPublicKey, err := parsePublicSSHKey(publicKey)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("failed to parse public_key as SSH key: %s", err)), nil
}
response, err := b.pathSignIssueCertificateHelper(ctx, req, data, role, userPublicKey)
if err != nil {
return nil, err
}
if response.IsError() {
return response, nil
}
// Additional to sign response
response.Data["private_key"] = privateKey
response.Data["private_key_type"] = keySpecs.Type
return response, nil
}
func extractKeySpecs(role *sshRole, data *framework.FieldData) (*keySpecs, error) {
keyType := data.Get("key_type").(string)
keyBits := data.Get("key_bits").(int)
keySpecs := keySpecs{
Type: keyType,
Bits: keyBits,
}
keyTypeToMapKey := createKeyTypeToMapKey(keyType, keyBits)
if len(role.AllowedUserKeyTypesLengths) != 0 {
var keyAllowed bool
var bitsAllowed bool
keyTypeAliasesLoop:
for _, keyTypeAlias := range keyTypeToMapKey[keyType] {
allowedValues, allowed := role.AllowedUserKeyTypesLengths[keyTypeAlias]
if !allowed {
continue
}
keyAllowed = true
for _, value := range allowedValues {
if value == keyBits {
bitsAllowed = true
break keyTypeAliasesLoop
}
}
}
if !keyAllowed {
return nil, errors.New("provided key_type value not in allowed_user_key_types")
}
if !bitsAllowed {
return nil, errors.New("provided key_bits value not in list of role's allowed_user_key_types")
}
}
return &keySpecs, nil
}
const pathIssueHelpSyn = `
Request a certificate using a certain role with the provided details.
`
const pathIssueHelpDesc = `
This path allows requesting a certificate to be issued according to the
policy of the given role. The certificate will only be issued if the
requested details are allowed by the role policy.
This path returns a certificate and a private key. If you want a workflow
that does not expose a private key, generate a CSR locally and use the
sign path instead.
`