Skip to content

Commit b9188ba

Browse files
authored
Add ce changes for ssh managed keys (#30061)
* add ce changes for ssh managed keys * fix key gen and storage logic * update to include managed key name and uuid in storage * change to ssh.PublicKey and add ssh.Signer functions * fix managed key stored values
1 parent bf0a73b commit b9188ba

File tree

4 files changed

+183
-54
lines changed

4 files changed

+183
-54
lines changed

builtin/logical/ssh/backend.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"sync"
1010

1111
"github.com/hashicorp/vault/sdk/framework"
12+
"github.com/hashicorp/vault/sdk/helper/errutil"
1213
"github.com/hashicorp/vault/sdk/helper/salt"
1314
"github.com/hashicorp/vault/sdk/logical"
1415
)
@@ -17,9 +18,10 @@ const operationPrefixSSH = "ssh"
1718

1819
type backend struct {
1920
*framework.Backend
20-
view logical.Storage
21-
salt *salt.Salt
22-
saltMutex sync.RWMutex
21+
view logical.Storage
22+
salt *salt.Salt
23+
saltMutex sync.RWMutex
24+
backendUUID string
2325
}
2426

2527
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
@@ -77,6 +79,8 @@ func Backend(conf *logical.BackendConfig) (*backend, error) {
7779
Invalidate: b.invalidate,
7880
BackendType: logical.TypeLogical,
7981
}
82+
83+
b.backendUUID = conf.BackendUUID
8084
return &b, nil
8185
}
8286

@@ -112,6 +116,18 @@ func (b *backend) invalidate(_ context.Context, key string) {
112116
}
113117
}
114118

119+
func (b *backend) GetManagedKeyView() (logical.ManagedKeySystemView, error) {
120+
managedKeyView, ok := b.System().(logical.ManagedKeySystemView)
121+
if !ok {
122+
return nil, errutil.InternalError{Err: "unsupported system view"}
123+
}
124+
return managedKeyView, nil
125+
}
126+
127+
func (b *backend) BackendUUID() string {
128+
return b.backendUUID
129+
}
130+
115131
const backendHelp = `
116132
The SSH backend generates credentials allowing clients to establish SSH
117133
connections to remote hosts.
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package managed_key
5+
6+
import (
7+
"io"
8+
9+
"github.com/hashicorp/vault/sdk/logical"
10+
"golang.org/x/crypto/ssh"
11+
)
12+
13+
type ManagedKeyInfo struct {
14+
publicKey ssh.PublicKey
15+
Name NameKey
16+
Uuid UUIDKey
17+
}
18+
19+
type managedKeyId interface {
20+
String() string
21+
}
22+
23+
type SSHManagedKeyView interface {
24+
BackendUUID() string
25+
GetManagedKeyView() (logical.ManagedKeySystemView, error)
26+
GetRandomReader() io.Reader
27+
}
28+
29+
type (
30+
UUIDKey string
31+
NameKey string
32+
)
33+
34+
func (u UUIDKey) String() string {
35+
return string(u)
36+
}
37+
38+
func (n NameKey) String() string {
39+
return string(n)
40+
}
41+
42+
func (m ManagedKeyInfo) PublicKey() ssh.PublicKey {
43+
return m.publicKey
44+
}
45+
46+
func (m ManagedKeyInfo) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
47+
return nil, nil
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build !enterprise
5+
6+
package managed_key
7+
8+
import (
9+
"context"
10+
"errors"
11+
)
12+
13+
func GetManagedKeyInfo(ctx context.Context, mkv SSHManagedKeyView, keyId managedKeyId) (*ManagedKeyInfo, error) {
14+
return nil, errors.New("managed keys are supported within enterprise edition only")
15+
}

builtin/logical/ssh/path_config_ca.go

+101-51
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"io"
1818

1919
multierror "github.com/hashicorp/go-multierror"
20+
"github.com/hashicorp/vault/builtin/logical/ssh/managed_key"
2021
"github.com/hashicorp/vault/sdk/framework"
2122
"github.com/hashicorp/vault/sdk/helper/cryptoutil"
2223
"github.com/hashicorp/vault/sdk/logical"
@@ -31,12 +32,19 @@ const (
3132
caPublicKeyStoragePathDeprecated = "public_key"
3233
caPrivateKeyStoragePath = "config/ca_private_key"
3334
caPrivateKeyStoragePathDeprecated = "config/ca_bundle"
35+
caManagedKeyStoragePath = "config/ca_managed_key"
3436
)
3537

3638
type keyStorageEntry struct {
3739
Key string `json:"key" structs:"key" mapstructure:"key"`
3840
}
3941

42+
type managedKeyStorageEntry struct {
43+
KeyId managed_key.UUIDKey `json:"key_id" structs:"key_id" mapstructure:"key_id"`
44+
KeyName managed_key.NameKey `json:"key_name" structs:"key_name" mapstructure:"key_name"`
45+
PublicKey string `json:"public_key" structs:"public_key" mapstructure:"public_key"`
46+
}
47+
4048
func pathConfigCA(b *backend) *framework.Path {
4149
return &framework.Path{
4250
Pattern: "config/ca",
@@ -56,7 +64,7 @@ func pathConfigCA(b *backend) *framework.Path {
5664
},
5765
"generate_signing_key": {
5866
Type: framework.TypeBool,
59-
Description: `Generate SSH key pair internally rather than use the private_key and public_key fields.`,
67+
Description: `Generate SSH key pair internally rather than use the private_key and public_key fields. If managed key config is provided, this field is ignored.`,
6068
Default: true,
6169
},
6270
"key_type": {
@@ -69,6 +77,14 @@ func pathConfigCA(b *backend) *framework.Path {
6977
Description: `Specifies the desired key bits when generating variable-length keys (such as when key_type="ssh-rsa") or which NIST P-curve to use when key_type="ec" (256, 384, or 521).`,
7078
Default: 0,
7179
},
80+
"managed_key_name": {
81+
Type: framework.TypeString,
82+
Description: `The name of the managed key to use. When using a managed key, this field or managed_key_id is required.`,
83+
},
84+
"managed_key_id": {
85+
Type: framework.TypeString,
86+
Description: `The id of the managed key to use. When using a managed key, this field or managed_key_name is required.`,
87+
},
7288
},
7389

7490
Operations: map[logical.Operation]framework.OperationHandler{
@@ -189,67 +205,75 @@ func (b *backend) pathConfigCAUpdate(ctx context.Context, req *logical.Request,
189205
publicKey := data.Get("public_key").(string)
190206
privateKey := data.Get("private_key").(string)
191207

192-
var generateSigningKey bool
208+
managedKeyName := data.Get("managed_key_name").(string)
209+
managedKeyID := data.Get("managed_key_id").(string)
193210

194-
generateSigningKeyRaw, ok := data.GetOk("generate_signing_key")
195-
switch {
196-
// explicitly set true
197-
case ok && generateSigningKeyRaw.(bool):
198-
if publicKey != "" || privateKey != "" {
199-
return logical.ErrorResponse("public_key and private_key must not be set when generate_signing_key is set to true"), nil
200-
}
211+
useManagedKey := managedKeyName != "" || managedKeyID != ""
201212

202-
generateSigningKey = true
213+
generateSigningKey := data.Get("generate_signing_key").(bool)
203214

204-
// explicitly set to false, or not set and we have both a public and private key
205-
case ok, publicKey != "" && privateKey != "":
206-
if publicKey == "" {
207-
return logical.ErrorResponse("missing public_key"), nil
215+
if useManagedKey {
216+
generateSigningKey = false
217+
err = b.createManagedKey(ctx, req.Storage, managedKeyName, managedKeyID)
218+
if err != nil {
219+
return nil, err
208220
}
221+
} else {
222+
if publicKey != "" && privateKey != "" {
223+
_, err := ssh.ParsePrivateKey([]byte(privateKey))
224+
if err != nil {
225+
return logical.ErrorResponse(fmt.Sprintf("Unable to parse private_key as an SSH private key: %v", err)), nil
226+
}
209227

210-
if privateKey == "" {
211-
return logical.ErrorResponse("missing private_key"), nil
212-
}
228+
_, err = parsePublicSSHKey(publicKey)
229+
if err != nil {
230+
return logical.ErrorResponse(fmt.Sprintf("Unable to parse public_key as an SSH public key: %v", err)), nil
231+
}
232+
} else if generateSigningKey {
233+
keyType := data.Get("key_type").(string)
234+
keyBits := data.Get("key_bits").(int)
213235

214-
_, err := ssh.ParsePrivateKey([]byte(privateKey))
215-
if err != nil {
216-
return logical.ErrorResponse(fmt.Sprintf("Unable to parse private_key as an SSH private key: %v", err)), nil
236+
publicKey, privateKey, err = generateSSHKeyPair(b.Backend.GetRandomReader(), keyType, keyBits)
237+
if err != nil {
238+
return nil, err
239+
}
240+
} else {
241+
return logical.ErrorResponse("if generate_signing_key is false, either both public_key and private_key or a managed key must be provided"), nil
217242
}
218243

219-
_, err = parsePublicSSHKey(publicKey)
244+
errResp, err := createStoredKey(ctx, req.Storage, publicKey, privateKey)
220245
if err != nil {
221-
return logical.ErrorResponse(fmt.Sprintf("Unable to parse public_key as an SSH public key: %v", err)), nil
246+
return nil, err
247+
}
248+
if errResp != nil {
249+
return errResp, nil
222250
}
223-
224-
// not set and no public/private key provided so generate
225-
case publicKey == "" && privateKey == "":
226-
generateSigningKey = true
227-
228-
// not set, but one or the other supplied
229-
default:
230-
return logical.ErrorResponse("only one of public_key and private_key set; both must be set to use, or both must be blank to auto-generate"), nil
231251
}
232252

233253
if generateSigningKey {
234-
keyType := data.Get("key_type").(string)
235-
keyBits := data.Get("key_bits").(int)
236-
237-
publicKey, privateKey, err = generateSSHKeyPair(b.Backend.GetRandomReader(), keyType, keyBits)
238-
if err != nil {
239-
return nil, err
254+
response := &logical.Response{
255+
Data: map[string]interface{}{
256+
"public_key": publicKey,
257+
},
240258
}
259+
260+
return response, nil
241261
}
242262

263+
return nil, nil
264+
}
265+
266+
func createStoredKey(ctx context.Context, s logical.Storage, publicKey, privateKey string) (*logical.Response, error) {
243267
if publicKey == "" || privateKey == "" {
244268
return nil, fmt.Errorf("failed to generate or parse the keys")
245269
}
246270

247-
publicKeyEntry, err := caKey(ctx, req.Storage, caPublicKey)
271+
publicKeyEntry, err := caKey(ctx, s, caPublicKey)
248272
if err != nil {
249273
return nil, fmt.Errorf("failed to read CA public key: %w", err)
250274
}
251275

252-
privateKeyEntry, err := caKey(ctx, req.Storage, caPrivateKey)
276+
privateKeyEntry, err := caKey(ctx, s, caPrivateKey)
253277
if err != nil {
254278
return nil, fmt.Errorf("failed to read CA private key: %w", err)
255279
}
@@ -266,7 +290,7 @@ func (b *backend) pathConfigCAUpdate(ctx context.Context, req *logical.Request,
266290
}
267291

268292
// Save the public key
269-
err = req.Storage.Put(ctx, entry)
293+
err = s.Put(ctx, entry)
270294
if err != nil {
271295
return nil, err
272296
}
@@ -279,32 +303,22 @@ func (b *backend) pathConfigCAUpdate(ctx context.Context, req *logical.Request,
279303
}
280304

281305
// Save the private key
282-
err = req.Storage.Put(ctx, entry)
306+
err = s.Put(ctx, entry)
283307
if err != nil {
284308
var mErr *multierror.Error
285309

286310
mErr = multierror.Append(mErr, fmt.Errorf("failed to store CA private key: %w", err))
287311

288312
// If storing private key fails, the corresponding public key should be
289313
// removed
290-
if delErr := req.Storage.Delete(ctx, caPublicKeyStoragePath); delErr != nil {
314+
if delErr := s.Delete(ctx, caPublicKeyStoragePath); delErr != nil {
291315
mErr = multierror.Append(mErr, fmt.Errorf("failed to cleanup CA public key: %w", delErr))
292316
return nil, mErr
293317
}
294318

295319
return nil, err
296320
}
297321

298-
if generateSigningKey {
299-
response := &logical.Response{
300-
Data: map[string]interface{}{
301-
"public_key": publicKey,
302-
},
303-
}
304-
305-
return response, nil
306-
}
307-
308322
return nil, nil
309323
}
310324

@@ -406,3 +420,39 @@ func generateSSHKeyPair(randomSource io.Reader, keyType string, keyBits int) (st
406420

407421
return string(ssh.MarshalAuthorizedKey(public)), string(pem.EncodeToMemory(privateBlock)), nil
408422
}
423+
424+
func (b *backend) createManagedKey(ctx context.Context, s logical.Storage, managedKeyName, managedKeyId string) error {
425+
var keyId managed_key.UUIDKey
426+
var keyName managed_key.NameKey
427+
var keyInfo *managed_key.ManagedKeyInfo
428+
var err error
429+
430+
if managedKeyId != "" {
431+
keyId = managed_key.UUIDKey(managedKeyId)
432+
keyInfo, err = managed_key.GetManagedKeyInfo(ctx, b, keyId)
433+
} else if managedKeyName != "" {
434+
keyName = managed_key.NameKey(managedKeyName)
435+
keyInfo, err = managed_key.GetManagedKeyInfo(ctx, b, keyName)
436+
}
437+
438+
if err != nil {
439+
return fmt.Errorf("error retrieving public key: %s", err)
440+
}
441+
442+
entry, err := logical.StorageEntryJSON(caManagedKeyStoragePath, &managedKeyStorageEntry{
443+
PublicKey: string(keyInfo.PublicKey().Marshal()),
444+
KeyName: keyInfo.Name,
445+
KeyId: keyInfo.Uuid,
446+
})
447+
if err != nil {
448+
return fmt.Errorf("error creating storage entry: %s", err)
449+
}
450+
451+
// Save the public key
452+
err = s.Put(ctx, entry)
453+
if err != nil {
454+
return fmt.Errorf("error writing key entry to storage: %s", err)
455+
}
456+
457+
return nil
458+
}

0 commit comments

Comments
 (0)