not_before_duration added to SSH (#15250)

* add-not-before-duration-to-ssh

* Missing field

* Adding tests

* changelog file

* Backend test

* Requested changes

* Update builtin/logical/ssh/path_roles.go

Co-authored-by: Alexander Scheel <alexander.m.scheel@gmail.com>
This commit is contained in:
Gabriel Santos 2022-05-12 13:50:40 +01:00 committed by GitHub
parent ca44b5a3e0
commit 469ad6d09a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 122 additions and 2 deletions

View File

@ -1682,6 +1682,100 @@ func TestBackend_DefExtTemplatingDisabled(t *testing.T) {
} }
} }
func TestSSHBackend_ValidateNotBeforeDuration(t *testing.T) {
config := logical.TestBackendConfig()
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatalf("Cannot create backend: %s", err)
}
testCase := logicaltest.TestCase{
LogicalBackend: b,
Steps: []logicaltest.TestStep{
configCaStep(testCAPublicKey, testCAPrivateKey),
createRoleStep("testing", map[string]interface{}{
"key_type": "ca",
"allow_host_certificates": true,
"allowed_domains": "example.com,example.org",
"allow_subdomains": true,
"default_critical_options": map[string]interface{}{
"option": "value",
},
"default_extensions": map[string]interface{}{
"extension": "extended",
},
"not_before_duration": "300s",
}),
signCertificateStep("testing", "vault-root-22608f5ef173aabf700797cb95c5641e792698ec6380e8e1eb55523e39aa5e51", ssh.HostCert, []string{"dummy.example.org", "second.example.com"}, map[string]string{
"option": "value",
}, map[string]string{
"extension": "extended",
},
2*time.Hour+5*time.Minute-30*time.Second, map[string]interface{}{
"public_key": publicKey2,
"ttl": "2h",
"cert_type": "host",
"valid_principals": "dummy.example.org,second.example.com",
}),
createRoleStep("testing", map[string]interface{}{
"key_type": "ca",
"allow_host_certificates": true,
"allowed_domains": "example.com,example.org",
"allow_subdomains": true,
"default_critical_options": map[string]interface{}{
"option": "value",
},
"default_extensions": map[string]interface{}{
"extension": "extended",
},
"not_before_duration": "2h",
}),
signCertificateStep("testing", "vault-root-22608f5ef173aabf700797cb95c5641e792698ec6380e8e1eb55523e39aa5e51", ssh.HostCert, []string{"dummy.example.org", "second.example.com"}, map[string]string{
"option": "value",
}, map[string]string{
"extension": "extended",
},
4*time.Hour-30*time.Second, map[string]interface{}{
"public_key": publicKey2,
"ttl": "2h",
"cert_type": "host",
"valid_principals": "dummy.example.org,second.example.com",
}),
createRoleStep("testing", map[string]interface{}{
"key_type": "ca",
"allow_host_certificates": true,
"allowed_domains": "example.com,example.org",
"allow_subdomains": true,
"default_critical_options": map[string]interface{}{
"option": "value",
},
"default_extensions": map[string]interface{}{
"extension": "extended",
},
"not_before_duration": "30s",
}),
signCertificateStep("testing", "vault-root-22608f5ef173aabf700797cb95c5641e792698ec6380e8e1eb55523e39aa5e51", ssh.HostCert, []string{"dummy.example.org", "second.example.com"}, map[string]string{
"option": "value",
}, map[string]string{
"extension": "extended",
},
2*time.Hour, map[string]interface{}{
"public_key": publicKey2,
"ttl": "2h",
"cert_type": "host",
"valid_principals": "dummy.example.org,second.example.com",
}),
},
}
logicaltest.Test(t, testCase)
}
func getSshCaTestCluster(t *testing.T, userIdentity string) (*vault.TestCluster, string) { func getSshCaTestCluster(t *testing.T, userIdentity string) (*vault.TestCluster, string) {
coreConfig := &vault.CoreConfig{ coreConfig := &vault.CoreConfig{
CredentialBackends: map[string]logical.Factory{ CredentialBackends: map[string]logical.Factory{

View File

@ -255,6 +255,7 @@ func TestSSH_ConfigCAKeyTypes(t *testing.T) {
"allowed_users": "*", "allowed_users": "*",
"key_type": "ca", "key_type": "ca",
"ttl": "30s", "ttl": "30s",
"not_before_duration": "2h",
} }
roleReq := &logical.Request{ roleReq := &logical.Request{
Operation: logical.UpdateOperation, Operation: logical.UpdateOperation,

View File

@ -28,7 +28,7 @@ const (
// Present version of the sshRole struct; when adding a new field or are // Present version of the sshRole struct; when adding a new field or are
// needing to perform a migration, increment this struct and read the note // needing to perform a migration, increment this struct and read the note
// in checkUpgrade(...). // in checkUpgrade(...).
roleEntryVersion = 2 roleEntryVersion = 3
) )
// Structure that represents a role in SSH backend. This is a common role structure // Structure that represents a role in SSH backend. This is a common role structure
@ -65,6 +65,7 @@ type sshRole struct {
AllowedUserKeyTypesLengths map[string][]int `mapstructure:"allowed_user_key_types_lengths" json:"allowed_user_key_types_lengths"` AllowedUserKeyTypesLengths map[string][]int `mapstructure:"allowed_user_key_types_lengths" json:"allowed_user_key_types_lengths"`
AlgorithmSigner string `mapstructure:"algorithm_signer" json:"algorithm_signer"` AlgorithmSigner string `mapstructure:"algorithm_signer" json:"algorithm_signer"`
Version int `mapstructure:"role_version" json:"role_version"` Version int `mapstructure:"role_version" json:"role_version"`
NotBeforeDuration time.Duration `mapstructure:"not_before_duration" json:"not_before_duration"`
} }
func pathListRoles(b *backend) *framework.Path { func pathListRoles(b *backend) *framework.Path {
@ -363,6 +364,16 @@ func pathRoles(b *backend) *framework.Path {
Name: "Signing Algorithm", Name: "Signing Algorithm",
}, },
}, },
"not_before_duration": {
Type: framework.TypeDurationSecond,
Default: 30,
Description: `
The duration that the SSH certificate should be backdated by at issuance.`,
DisplayAttrs: &framework.DisplayAttributes{
Name: "Not before duration",
Value: 30,
},
},
}, },
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
@ -555,6 +566,7 @@ func (b *backend) createCARole(allowedUsers, defaultUser, signer string, data *f
KeyType: KeyTypeCA, KeyType: KeyTypeCA,
AlgorithmSigner: signer, AlgorithmSigner: signer,
Version: roleEntryVersion, Version: roleEntryVersion,
NotBeforeDuration: time.Duration(data.Get("not_before_duration").(int)) * time.Second,
} }
if !role.AllowUserCertificates && !role.AllowHostCertificates { if !role.AllowUserCertificates && !role.AllowHostCertificates {
@ -672,6 +684,12 @@ func (b *backend) checkUpgrade(ctx context.Context, s logical.Storage, n string,
err = nil err = nil
} }
if result.Version < 3 {
modified = true
result.NotBeforeDuration = 30 * time.Second
result.Version = 3
}
// Add new migrations just before here. // Add new migrations just before here.
// //
// Condition copied from PKI builtin. // Condition copied from PKI builtin.
@ -739,6 +757,7 @@ func (b *backend) parseRole(role *sshRole) (map[string]interface{}, error) {
"default_extensions_template": role.DefaultExtensionsTemplate, "default_extensions_template": role.DefaultExtensionsTemplate,
"allowed_user_key_lengths": role.AllowedUserKeyTypesLengths, "allowed_user_key_lengths": role.AllowedUserKeyTypesLengths,
"algorithm_signer": role.AlgorithmSigner, "algorithm_signer": role.AlgorithmSigner,
"not_before_duration": role.NotBeforeDuration,
} }
case KeyTypeDynamic: case KeyTypeDynamic:
result = map[string]interface{}{ result = map[string]interface{}{

View File

@ -580,7 +580,7 @@ func (b *creationBundle) sign() (retCert *ssh.Certificate, retErr error) {
Key: b.PublicKey, Key: b.PublicKey,
KeyId: b.KeyID, KeyId: b.KeyID,
ValidPrincipals: b.ValidPrincipals, ValidPrincipals: b.ValidPrincipals,
ValidAfter: uint64(now.Add(-30 * time.Second).In(time.UTC).Unix()), ValidAfter: uint64(now.Add(-b.Role.NotBeforeDuration).In(time.UTC).Unix()),
ValidBefore: uint64(now.Add(b.TTL).In(time.UTC).Unix()), ValidBefore: uint64(now.Add(b.TTL).In(time.UTC).Unix()),
CertType: b.CertificateType, CertType: b.CertificateType,
Permissions: ssh.Permissions{ Permissions: ssh.Permissions{

3
changelog/15250.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
secrets/ssh: Support for `add_before_duration` in SSH
```

View File

@ -36,6 +36,7 @@ const CA_FIELDS = [
'allowSubdomains', 'allowSubdomains',
'allowUserKeyIds', 'allowUserKeyIds',
'keyIdFormat', 'keyIdFormat',
'notBeforeDuration'
]; ];
export default Model.extend({ export default Model.extend({

View File

@ -831,6 +831,8 @@ to the restrictions contained in the role named in the endpoint.
- `extensions` `(map<string|string>: "")`  Specifies a map of the extensions - `extensions` `(map<string|string>: "")`  Specifies a map of the extensions
that the certificate should be signed for. Defaults to none. that the certificate should be signed for. Defaults to none.
- `not_before_duration` `(duration: "30s")`  Specifies the duration by which to backdate the `ValidAfter` property.
### Sample Payload ### Sample Payload
```json ```json