129 lines
3.3 KiB
Go
129 lines
3.3 KiB
Go
package ssh
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/hashicorp/vault/helper/errutil"
|
|
"github.com/hashicorp/vault/logical"
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
"golang.org/x/crypto/ssh"
|
|
"crypto/rsa"
|
|
"encoding/pem"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
)
|
|
|
|
func pathConfigCA(b *backend) *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "config/ca",
|
|
Fields: map[string]*framework.FieldSchema{
|
|
"private_key": &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `Private half of the SSH key that will be used to sign certificates.`,
|
|
},
|
|
"public_key": &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `Public half of the SSH key that will be used to sign certificates.`,
|
|
},
|
|
"generate_signing_key": &framework.FieldSchema{
|
|
Type: framework.TypeBool,
|
|
Description: `Generate SSH key pair internally rather than use the private_key and public_key fields.`,
|
|
Default: true,
|
|
},
|
|
},
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
logical.UpdateOperation: b.pathCAWrite,
|
|
},
|
|
|
|
HelpSynopsis: `Set the SSH private key used for signing certificates.`,
|
|
HelpDescription: `This sets the CA information used for certificates generated by this
|
|
by this mount. The fields must be in the standard private and public SSH format.
|
|
|
|
For security reasons, the private key cannot be retrieved later.`,
|
|
}
|
|
}
|
|
|
|
func (b *backend) pathCAWrite(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
|
|
var publicKey, privateKey string
|
|
var err error
|
|
if data.Get("generate_signing_key").(bool) {
|
|
publicKey, privateKey, err = generateSSHKeyPair()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
publicKey, privateKey, err = parseSSHKeyPair(data)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
err = req.Storage.Put(&logical.StorageEntry{
|
|
Key: "public_key",
|
|
Value: []byte(publicKey),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bundle := signingBundle{
|
|
Certificate: privateKey,
|
|
}
|
|
|
|
entry, err := logical.StorageEntryJSON("config/ca_bundle", bundle)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = req.Storage.Put(entry)
|
|
return nil, err
|
|
}
|
|
|
|
|
|
func generateSSHKeyPair() (string, string, error) {
|
|
privateSeed, err := rsa.GenerateKey(rand.Reader, 4096)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
privateBlock := &pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Headers: nil,
|
|
Bytes: x509.MarshalPKCS1PrivateKey(privateSeed),
|
|
}
|
|
|
|
public, err := ssh.NewPublicKey(&privateSeed.PublicKey)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
return string(ssh.MarshalAuthorizedKey(public)), string(pem.EncodeToMemory(privateBlock)), nil
|
|
}
|
|
|
|
func parseSSHKeyPair(data *framework.FieldData) (string, string, error) {
|
|
|
|
publicKey := data.Get("public_key").(string)
|
|
if publicKey == "" {
|
|
return "", "", errutil.UserError{Err: `missing public_key`}
|
|
}
|
|
|
|
privateKey := data.Get("private_key").(string)
|
|
if privateKey == "" {
|
|
return "", "", errutil.UserError{Err: `missing public_key`}
|
|
}
|
|
|
|
_, err := ssh.ParsePrivateKey([]byte(privateKey))
|
|
if err != nil {
|
|
return "", "", errutil.UserError{Err: fmt.Sprintf(`Unable to parse "private_key" as an SSH private key: %s`, err)}
|
|
}
|
|
|
|
_, err = parsePublicSSHKey(publicKey)
|
|
if err != nil {
|
|
return "", "", errutil.UserError{Err: fmt.Sprintf(`Unable to parse "public_key" as an SSH public key: %s`, err)}
|
|
}
|
|
|
|
return publicKey, privateKey, nil
|
|
}
|