mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
* ssh observations and tests * remove unnecessary comments * add metadata in comments * add more assertions, fix test * fix test Co-authored-by: miagilepner <mia.epner@hashicorp.com>
This commit is contained in:
parent
539e30c4cd
commit
87c9b9470b
7 changed files with 202 additions and 38 deletions
|
|
@ -135,6 +135,14 @@ SjOQL/GkH1nkRcDS9++aAAAAAmNhAQID
|
|||
dockerImageTagSupportsNoRSA1 = "8.4_p1-r3-ls48"
|
||||
)
|
||||
|
||||
var caObservationFields = []string{
|
||||
"ttl", "max_ttl", "allow_user_certificates", "allow_host_certificates",
|
||||
"allow_bare_domains", "allow_subdomains", "allow_user_key_ids",
|
||||
"allowed_users_template", "allowed_domains_template", "default_user_template",
|
||||
"default_extensions_template", "algorithm_signer", "not_before_duration",
|
||||
"allow_empty_principals",
|
||||
}
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func prepareTestContainer(t *testing.T, tag, caPublicKeyPEM string) (func(), string) {
|
||||
|
|
@ -773,10 +781,11 @@ func TestSSHBackend_VerifyEcho(t *testing.T) {
|
|||
expectedData := map[string]interface{}{
|
||||
"message": api.VerifyEchoResponse,
|
||||
}
|
||||
obsRecorder := observations.NewTestObservationRecorder()
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
LogicalFactory: newTestingFactory(t, nil),
|
||||
LogicalFactory: newTestingFactory(t, obsRecorder),
|
||||
Steps: []logicaltest.TestStep{
|
||||
testVerifyWrite(t, verifyData, expectedData),
|
||||
testVerifyWrite(t, verifyData, expectedData, obsRecorder),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -1007,7 +1016,7 @@ cKumubUxOfFdy1ZvAAAAEm5jY0BtYnAudWJudC5sb2NhbA==
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(caPublicKey, caPrivateKey),
|
||||
configCaStep(caPublicKey, caPrivateKey, obsRecorder),
|
||||
testRoleWrite(t, "testcarole", roleOptions, obsRecorder),
|
||||
{
|
||||
Operation: logical.UpdateOperation,
|
||||
|
|
@ -1050,19 +1059,27 @@ cKumubUxOfFdy1ZvAAAAEm5jY0BtYnAudWJudC5sb2NhbA==
|
|||
if !expectError && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obs := obsRecorder.LastObservationOfType(ObservationTypeSSHSign)
|
||||
if obs == nil {
|
||||
return errors.New("no SSH sign observation recorded")
|
||||
}
|
||||
if obs.Data["role_name"] != "testcarole" {
|
||||
return fmt.Errorf("expected role_name %q, got %q", "testcarole", obs.Data["role_name"])
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
testIssueCert("testcarole", "ec", testUserName, sshAddress, expectError),
|
||||
testIssueCert("testcarole", "ed25519", testUserName, sshAddress, expectError),
|
||||
testIssueCert("testcarole", "rsa", testUserName, sshAddress, expectError),
|
||||
testIssueCert("testcarole", "ec", testUserName, sshAddress, expectError, obsRecorder),
|
||||
testIssueCert("testcarole", "ed25519", testUserName, sshAddress, expectError, obsRecorder),
|
||||
testIssueCert("testcarole", "rsa", testUserName, sshAddress, expectError, obsRecorder),
|
||||
},
|
||||
}
|
||||
|
||||
logicaltest.Test(t, testCase)
|
||||
}
|
||||
|
||||
func testIssueCert(role string, keyType string, testUserName string, sshAddress string, expectError bool) logicaltest.TestStep {
|
||||
func testIssueCert(role string, keyType string, testUserName string, sshAddress string, expectError bool, obsRecorder *observations.TestObservationRecorder) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "issue/" + role,
|
||||
|
|
@ -1105,6 +1122,32 @@ func testIssueCert(role string, keyType string, testUserName string, sshAddress
|
|||
return err
|
||||
}
|
||||
|
||||
if obsRecorder == nil {
|
||||
return nil
|
||||
}
|
||||
obs := obsRecorder.LastObservationOfType(ObservationTypeSSHIssue)
|
||||
if obs == nil {
|
||||
return errors.New("no SSH issue observation recorded")
|
||||
}
|
||||
if obs.Data["role_name"] != role {
|
||||
return fmt.Errorf("expected role_name %q, got %q", role, obs.Data["role_name"])
|
||||
}
|
||||
if obs.Data["key_type"] == nil {
|
||||
return fmt.Errorf("missing key_type in observation metadata")
|
||||
}
|
||||
if obs.Data["certificate_type"] == nil {
|
||||
return fmt.Errorf("missing certificate_type in observation metadata")
|
||||
}
|
||||
if obs.Data["serial_number"] == nil {
|
||||
return fmt.Errorf("missing serial_number in observation metadata")
|
||||
}
|
||||
if obs.Data["key_id"] == nil {
|
||||
return fmt.Errorf("missing key_id in observation metadata")
|
||||
}
|
||||
if _, exists := obs.Data["ttl"]; !exists {
|
||||
return fmt.Errorf("missing ttl in observation metadata")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
@ -1186,7 +1229,7 @@ cKumubUxOfFdy1ZvAAAAEm5jY0BtYnAudWJudC5sb2NhbA==
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, obsRecorder),
|
||||
testRoleWrite(t, "testcarole", roleOptionsOldEntry, obsRecorder),
|
||||
testRoleWrite(t, "testcarole", roleOptionsUpgradedEntry, obsRecorder),
|
||||
{
|
||||
|
|
@ -1243,7 +1286,7 @@ func TestBackend_AbleToRetrievePublicKey(t *testing.T) {
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, nil),
|
||||
|
||||
{
|
||||
Operation: logical.ReadOperation,
|
||||
|
|
@ -1325,7 +1368,7 @@ func TestBackend_ValidPrincipalsValidatedForHostCertificates(t *testing.T) {
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, nil),
|
||||
|
||||
createRoleStep("testing", map[string]interface{}{
|
||||
"key_type": "ca",
|
||||
|
|
@ -1368,7 +1411,7 @@ func TestBackend_OptionsOverrideDefaults(t *testing.T) {
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, nil),
|
||||
|
||||
createRoleStep("testing", map[string]interface{}{
|
||||
"key_type": "ca",
|
||||
|
|
@ -1415,7 +1458,7 @@ func TestBackend_EmptyPrincipals(t *testing.T) {
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, nil),
|
||||
createRoleStep("no_user_principals", map[string]interface{}{
|
||||
"key_type": "ca",
|
||||
"allow_user_certificates": true,
|
||||
|
|
@ -1489,7 +1532,7 @@ func TestBackend_AllowedUserKeyLengths(t *testing.T) {
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, nil),
|
||||
createRoleStep("weakkey", map[string]interface{}{
|
||||
"key_type": "ca",
|
||||
"allow_user_certificates": true,
|
||||
|
|
@ -1662,7 +1705,7 @@ func TestBackend_CustomKeyIDFormat(t *testing.T) {
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, nil),
|
||||
|
||||
createRoleStep("customrole", map[string]interface{}{
|
||||
"key_type": "ca",
|
||||
|
|
@ -1711,7 +1754,7 @@ func TestBackend_DisallowUserProvidedKeyIDs(t *testing.T) {
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, nil),
|
||||
|
||||
createRoleStep("testing", map[string]interface{}{
|
||||
"key_type": "ca",
|
||||
|
|
@ -1976,7 +2019,7 @@ func TestSSHBackend_ValidateNotBeforeDuration(t *testing.T) {
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, nil),
|
||||
|
||||
createRoleStep("testing", map[string]interface{}{
|
||||
"key_type": "ca",
|
||||
|
|
@ -2071,7 +2114,7 @@ func TestSSHBackend_IssueSign(t *testing.T) {
|
|||
testCase := logicaltest.TestCase{
|
||||
LogicalBackend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey),
|
||||
configCaStep(testCAPublicKey, testCAPrivateKey, nil),
|
||||
|
||||
createRoleStep("testing", map[string]interface{}{
|
||||
"key_type": "otp",
|
||||
|
|
@ -2304,7 +2347,7 @@ func testAllowedUsersTemplate(t *testing.T, testAllowedUsersTemplate string,
|
|||
)
|
||||
}
|
||||
|
||||
func configCaStep(caPublicKey, caPrivateKey string) logicaltest.TestStep {
|
||||
func configCaStep(caPublicKey, caPrivateKey string, obsRecorder *observations.TestObservationRecorder) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config/ca",
|
||||
|
|
@ -2312,6 +2355,17 @@ func configCaStep(caPublicKey, caPrivateKey string) logicaltest.TestStep {
|
|||
"public_key": caPublicKey,
|
||||
"private_key": caPrivateKey,
|
||||
},
|
||||
Check: func(r *logical.Response) error {
|
||||
if obsRecorder == nil {
|
||||
return nil
|
||||
}
|
||||
obs := obsRecorder.LastObservationOfType(ObservationTypeSSHConfigCAWrite)
|
||||
if obs == nil {
|
||||
return errors.New("no SSH config CA write observation recorded")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2540,7 +2594,7 @@ func testConfigZeroAddressRead(t *testing.T, expected map[string]interface{}, ob
|
|||
}
|
||||
}
|
||||
|
||||
func testVerifyWrite(t *testing.T, data map[string]interface{}, expected map[string]interface{}) logicaltest.TestStep {
|
||||
func testVerifyWrite(t *testing.T, data map[string]interface{}, expected map[string]interface{}, obsRecorder *observations.TestObservationRecorder) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: fmt.Sprintf("verify"),
|
||||
|
|
@ -2558,6 +2612,17 @@ func testVerifyWrite(t *testing.T, data map[string]interface{}, expected map[str
|
|||
if !reflect.DeepEqual(ac, ex) {
|
||||
return fmt.Errorf("invalid response")
|
||||
}
|
||||
|
||||
if obsRecorder != nil && data["otp"] != api.VerifyEchoRequest {
|
||||
lastObservation := obsRecorder.LastObservationOfType(ObservationTypeSSHOTPVerify)
|
||||
if lastObservation == nil {
|
||||
return fmt.Errorf("missing OTP verify observation")
|
||||
}
|
||||
if lastObservation.Data["role_name"] == nil {
|
||||
return fmt.Errorf("missing role_name in OTP verify observation metadata")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
@ -2610,6 +2675,15 @@ func testRoleWrite(t *testing.T, name string, data map[string]interface{}, obsRe
|
|||
if lastObservation.Data["key_type"] != data["key_type"] {
|
||||
return fmt.Errorf("invalid observation data: \nactual:%#v\nexpected:%#v", lastObservation.Data["key_type"], data["key_type"])
|
||||
}
|
||||
|
||||
if data["key_type"] == KeyTypeCA {
|
||||
for _, field := range caObservationFields {
|
||||
if _, exists := lastObservation.Data[field]; !exists {
|
||||
return fmt.Errorf("missing CA-specific field %q in observation metadata for CA role", field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
@ -2670,6 +2744,15 @@ func testRoleRead(t *testing.T, roleName string, expected map[string]interface{}
|
|||
if lastObservation.Data["key_type"] != d.KeyType {
|
||||
return fmt.Errorf("invalid observation data: \nactual:%#v\nexpected:%#v", lastObservation.Data["key_type"], d.KeyType)
|
||||
}
|
||||
|
||||
if d.KeyType == KeyTypeCA {
|
||||
for _, field := range caObservationFields {
|
||||
if _, exists := lastObservation.Data[field]; !exists {
|
||||
return fmt.Errorf("missing CA-specific field %q in observation metadata for CA role", field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
@ -2754,12 +2837,14 @@ func testCredsWrite(t *testing.T, roleName string, data map[string]interface{},
|
|||
if lastObservation == nil {
|
||||
return fmt.Errorf("missing observation")
|
||||
}
|
||||
|
||||
if lastObservation.Data["role_name"] != roleName {
|
||||
return fmt.Errorf("invalid observation data: \nactual:%#v\nexpected:%#v", lastObservation.Data["role_name"], roleName)
|
||||
}
|
||||
if lastObservation.Data["key_type"] != KeyTypeOTP {
|
||||
return fmt.Errorf("invalid observation data: \nactual:%#v\nexpected:%#v", lastObservation.Data["key_type"], KeyTypeOTP)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
@ -2768,6 +2853,8 @@ func testCredsWrite(t *testing.T, roleName string, data map[string]interface{},
|
|||
func TestBackend_CleanupDynamicHostKeys(t *testing.T) {
|
||||
config := logical.TestBackendConfig()
|
||||
config.StorageView = &logical.InmemStorage{}
|
||||
obsRecorder := observations.NewTestObservationRecorder()
|
||||
config.ObservationRecorder = obsRecorder
|
||||
b, err := Backend(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -2790,6 +2877,9 @@ func TestBackend_CleanupDynamicHostKeys(t *testing.T) {
|
|||
require.NotNil(t, resp.Data)
|
||||
require.NotNil(t, resp.Data["message"])
|
||||
require.Contains(t, resp.Data["message"], "0 of 0")
|
||||
obs := obsRecorder.LastObservationOfType(ObservationTypeSSHTidyDynamicKeys)
|
||||
require.NotNil(t, obs)
|
||||
require.Equal(t, 0, obs.Data["keys_deleted"])
|
||||
// Write a bunch of bogus entries.
|
||||
for i := 0; i < 15; i++ {
|
||||
data := map[string]interface{}{
|
||||
|
|
@ -2809,6 +2899,9 @@ func TestBackend_CleanupDynamicHostKeys(t *testing.T) {
|
|||
require.NotNil(t, resp.Data)
|
||||
require.NotNil(t, resp.Data["message"])
|
||||
require.Contains(t, resp.Data["message"], "15 of 15")
|
||||
obs = obsRecorder.LastObservationOfType(ObservationTypeSSHTidyDynamicKeys)
|
||||
require.NotNil(t, obs)
|
||||
require.Equal(t, 15, obs.Data["keys_deleted"])
|
||||
|
||||
// Should have none left.
|
||||
resp, err = b.HandleRequest(context.Background(), cleanRequest)
|
||||
|
|
@ -2817,6 +2910,9 @@ func TestBackend_CleanupDynamicHostKeys(t *testing.T) {
|
|||
require.NotNil(t, resp.Data)
|
||||
require.NotNil(t, resp.Data["message"])
|
||||
require.Contains(t, resp.Data["message"], "0 of 0")
|
||||
obs = obsRecorder.LastObservationOfType(ObservationTypeSSHTidyDynamicKeys)
|
||||
require.NotNil(t, obs)
|
||||
require.Equal(t, 0, obs.Data["keys_deleted"])
|
||||
}
|
||||
|
||||
type pathAuthCheckerFunc func(t *testing.T, client *api.Client, path string, token string)
|
||||
|
|
|
|||
|
|
@ -36,4 +36,28 @@ const (
|
|||
|
||||
// ObservationTypeSSHLookup - Metadata: role_names ([]string)
|
||||
ObservationTypeSSHLookup = "ssh/lookup"
|
||||
|
||||
// ObservationTypeSSHConfigCARead - Metadata: none
|
||||
ObservationTypeSSHConfigCARead = "ssh/config/ca/read"
|
||||
// ObservationTypeSSHConfigCAWrite - Metadata: conditionally:
|
||||
// managed_key_name, managed_key_id (if using managed key), or key_type, key_bits (if generating)
|
||||
ObservationTypeSSHConfigCAWrite = "ssh/config/ca/write"
|
||||
// ObservationTypeSSHConfigCADelete - Metadata: none
|
||||
ObservationTypeSSHConfigCADelete = "ssh/config/ca/delete"
|
||||
|
||||
// ObservationTypeSSHSign - Metadata: role_name, key_type, certificate_type, ttl, serial_number,
|
||||
// key_id, and for CA roles: max_ttl, allow_user_certificates, allow_host_certificates,
|
||||
// allow_bare_domains, allow_subdomains, allow_user_key_ids, allowed_users_template,
|
||||
// allowed_domains_template, default_user_template, default_extensions_template,
|
||||
// algorithm_signer, not_before_duration, allow_empty_principals
|
||||
ObservationTypeSSHSign = "ssh/certificate/sign"
|
||||
// ObservationTypeSSHIssue - Metadata: role_name, key_type (from keySpecs), key_bits,
|
||||
// certificate_type, ttl, serial_number, key_id, and for CA roles: max_ttl,
|
||||
// allow_user_certificates, allow_host_certificates, allow_bare_domains, allow_subdomains,
|
||||
// allow_user_key_ids, allowed_users_template, allowed_domains_template, default_user_template,
|
||||
// default_extensions_template, algorithm_signer, not_before_duration, allow_empty_principals
|
||||
ObservationTypeSSHIssue = "ssh/certificate/issue"
|
||||
|
||||
// ObservationTypeSSHTidyDynamicKeys - Metadata: keys_deleted (int)
|
||||
ObservationTypeSSHTidyDynamicKeys = "ssh/tidy/dynamic-keys"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ func (b *backend) handleCleanupKeys(ctx context.Context, req *logical.Request, d
|
|||
}
|
||||
}
|
||||
|
||||
b.Backend.TryRecordObservationWithRequest(ctx, req, ObservationTypeSSHTidyDynamicKeys, map[string]interface{}{
|
||||
"keys_deleted": len(names),
|
||||
})
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"message": fmt.Sprintf("Removed %v of %v host keys.", len(names), len(names)),
|
||||
|
|
|
|||
|
|
@ -134,6 +134,8 @@ func (b *backend) pathConfigCARead(ctx context.Context, req *logical.Request, da
|
|||
return logical.ErrorResponse("keys haven't been configured yet"), nil
|
||||
}
|
||||
|
||||
b.Backend.TryRecordObservationWithRequest(ctx, req, ObservationTypeSSHConfigCARead, nil)
|
||||
|
||||
response := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"public_key": publicKey,
|
||||
|
|
@ -159,6 +161,9 @@ func (b *backend) pathConfigCADelete(ctx context.Context, req *logical.Request,
|
|||
if err := req.Storage.Delete(ctx, caManagedKeyStoragePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Backend.TryRecordObservationWithRequest(ctx, req, ObservationTypeSSHConfigCADelete, nil)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -247,12 +252,16 @@ func (b *backend) pathConfigCAUpdate(ctx context.Context, req *logical.Request,
|
|||
|
||||
generateSigningKey := data.Get("generate_signing_key").(bool)
|
||||
|
||||
metadata := make(map[string]interface{})
|
||||
|
||||
if useManagedKey {
|
||||
generateSigningKey = false
|
||||
err = b.createManagedKey(ctx, req.Storage, managedKeyName, managedKeyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata["managed_key_name"] = managedKeyName
|
||||
metadata["managed_key_id"] = managedKeyID
|
||||
} else {
|
||||
if publicKey != "" && privateKey != "" {
|
||||
_, err := ssh.ParsePrivateKey([]byte(privateKey))
|
||||
|
|
@ -272,6 +281,8 @@ func (b *backend) pathConfigCAUpdate(ctx context.Context, req *logical.Request,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata["key_type"] = keyType
|
||||
metadata["key_bits"] = keyBits
|
||||
} else {
|
||||
return logical.ErrorResponse("if generate_signing_key is false, either both public_key and private_key or a managed key must be provided"), nil
|
||||
}
|
||||
|
|
@ -282,6 +293,8 @@ func (b *backend) pathConfigCAUpdate(ctx context.Context, req *logical.Request,
|
|||
}
|
||||
}
|
||||
|
||||
b.Backend.TryRecordObservationWithRequest(ctx, req, ObservationTypeSSHConfigCAWrite, metadata)
|
||||
|
||||
if generateSigningKey {
|
||||
response := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
|
|
@ -105,10 +106,10 @@ func (b *backend) pathIssue(ctx context.Context, req *logical.Request, data *fra
|
|||
}
|
||||
|
||||
// Issue certificate
|
||||
return b.pathIssueCertificate(ctx, req, data, role, keySpecs)
|
||||
return b.pathIssueCertificate(ctx, req, data, role, keySpecs, roleName)
|
||||
}
|
||||
|
||||
func (b *backend) pathIssueCertificate(ctx context.Context, req *logical.Request, data *framework.FieldData, role *sshRole, keySpecs *keySpecs) (*logical.Response, error) {
|
||||
func (b *backend) pathIssueCertificate(ctx context.Context, req *logical.Request, data *framework.FieldData, role *sshRole, keySpecs *keySpecs, roleName string) (*logical.Response, error) {
|
||||
publicKey, privateKey, err := generateSSHKeyPair(rand.Reader, keySpecs.Type, keySpecs.Bits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -120,7 +121,7 @@ func (b *backend) pathIssueCertificate(ctx context.Context, req *logical.Request
|
|||
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)
|
||||
response, certMetadata, err := b.pathSignIssueCertificateHelper(ctx, req, data, role, userPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -132,6 +133,11 @@ func (b *backend) pathIssueCertificate(ctx context.Context, req *logical.Request
|
|||
response.Data["private_key"] = privateKey
|
||||
response.Data["private_key_type"] = keySpecs.Type
|
||||
|
||||
metadata := role.observationMetadata(roleName)
|
||||
metadata["key_type"] = keySpecs.Type
|
||||
metadata["key_bits"] = keySpecs.Bits
|
||||
maps.Copy(metadata, certMetadata)
|
||||
b.TryRecordObservationWithRequest(ctx, req, ObservationTypeSSHIssue, metadata)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,57 +54,57 @@ type creationBundle struct {
|
|||
Extensions map[string]string
|
||||
}
|
||||
|
||||
func (b *backend) pathSignIssueCertificateHelper(ctx context.Context, req *logical.Request, data *framework.FieldData, role *sshRole, publicKey ssh.PublicKey) (*logical.Response, error) {
|
||||
func (b *backend) pathSignIssueCertificateHelper(ctx context.Context, req *logical.Request, data *framework.FieldData, role *sshRole, publicKey ssh.PublicKey) (*logical.Response, map[string]interface{}, error) {
|
||||
// Note that these various functions always return "user errors" so we pass
|
||||
// them as 4xx values
|
||||
keyID, err := b.calculateKeyID(data, req, role, publicKey)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
return logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
|
||||
certificateType, err := b.calculateCertificateType(data, role)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
return logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
|
||||
var parsedPrincipals []string
|
||||
if certificateType == ssh.HostCert {
|
||||
parsedPrincipals, err = b.calculateValidPrincipals(data, req, role, "", role.AllowedDomains, role.AllowedDomainsTemplate, validateValidPrincipalForHosts(role))
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
return logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
} else {
|
||||
defaultPrincipal := role.DefaultUser
|
||||
if role.DefaultUserTemplate {
|
||||
defaultPrincipal, err = b.renderPrincipal(role.DefaultUser, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
parsedPrincipals, err = b.calculateValidPrincipals(data, req, role, defaultPrincipal, role.AllowedUsers, role.AllowedUsersTemplate, strutil.StrListContains)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
return logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
ttl, err := b.calculateTTL(data, role)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
return logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
|
||||
criticalOptions, err := b.calculateCriticalOptions(data, role)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
return logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
|
||||
extensions, addExtTemplatingWarning, err := b.calculateExtensions(data, req, role)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
return logical.ErrorResponse(err.Error()), nil, nil
|
||||
}
|
||||
|
||||
signer, err := b.getCASigner(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating signer: %w", err)
|
||||
return nil, nil, fmt.Errorf("error creating signer: %w", err)
|
||||
}
|
||||
|
||||
cBundle := creationBundle{
|
||||
|
|
@ -121,12 +121,12 @@ func (b *backend) pathSignIssueCertificateHelper(ctx context.Context, req *logic
|
|||
|
||||
certificate, err := cBundle.sign()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
signedSSHCertificate := ssh.MarshalAuthorizedKey(certificate)
|
||||
if len(signedSSHCertificate) == 0 {
|
||||
return nil, errors.New("error marshaling signed certificate")
|
||||
return nil, nil, errors.New("error marshaling signed certificate")
|
||||
}
|
||||
|
||||
response := &logical.Response{
|
||||
|
|
@ -140,7 +140,14 @@ func (b *backend) pathSignIssueCertificateHelper(ctx context.Context, req *logic
|
|||
response.AddWarning("default_extension templating enabled with at least one extension requiring identity templating. However, this request lacked identity entity information, causing one or more extensions to be skipped from the generated certificate.")
|
||||
}
|
||||
|
||||
return response, nil
|
||||
metadata := map[string]interface{}{
|
||||
"certificate_type": certificateType,
|
||||
"ttl": ttl.String(),
|
||||
"serial_number": strconv.FormatUint(certificate.Serial, 16),
|
||||
"key_id": keyID,
|
||||
}
|
||||
|
||||
return response, metadata, nil
|
||||
}
|
||||
|
||||
func (b *backend) renderPrincipal(principal string, req *logical.Request) (string, error) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package ssh
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
|
|
@ -82,10 +83,10 @@ func (b *backend) pathSign(ctx context.Context, req *logical.Request, data *fram
|
|||
return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", roleName)), nil
|
||||
}
|
||||
|
||||
return b.pathSignCertificate(ctx, req, data, role)
|
||||
return b.pathSignCertificate(ctx, req, data, role, roleName)
|
||||
}
|
||||
|
||||
func (b *backend) pathSignCertificate(ctx context.Context, req *logical.Request, data *framework.FieldData, role *sshRole) (*logical.Response, error) {
|
||||
func (b *backend) pathSignCertificate(ctx context.Context, req *logical.Request, data *framework.FieldData, role *sshRole, roleName string) (*logical.Response, error) {
|
||||
publicKey := data.Get("public_key").(string)
|
||||
if publicKey == "" {
|
||||
return logical.ErrorResponse("missing public_key"), nil
|
||||
|
|
@ -101,5 +102,18 @@ func (b *backend) pathSignCertificate(ctx context.Context, req *logical.Request,
|
|||
return logical.ErrorResponse(fmt.Sprintf("public_key failed to meet the key requirements: %s", err)), nil
|
||||
}
|
||||
|
||||
return b.pathSignIssueCertificateHelper(ctx, req, data, role, userPublicKey)
|
||||
response, certMetadata, err := b.pathSignIssueCertificateHelper(ctx, req, data, role, userPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
metadata := role.observationMetadata(roleName)
|
||||
maps.Copy(metadata, certMetadata)
|
||||
b.TryRecordObservationWithRequest(ctx, req, ObservationTypeSSHSign, metadata)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue