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:
parent
ca44b5a3e0
commit
469ad6d09a
|
@ -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{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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{}{
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
secrets/ssh: Support for `add_before_duration` in SSH
|
||||||
|
```
|
|
@ -36,6 +36,7 @@ const CA_FIELDS = [
|
||||||
'allowSubdomains',
|
'allowSubdomains',
|
||||||
'allowUserKeyIds',
|
'allowUserKeyIds',
|
||||||
'keyIdFormat',
|
'keyIdFormat',
|
||||||
|
'notBeforeDuration'
|
||||||
];
|
];
|
||||||
|
|
||||||
export default Model.extend({
|
export default Model.extend({
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue