mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-04 04:28:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			2791 lines
		
	
	
		
			91 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			2791 lines
		
	
	
		
			91 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) HashiCorp, Inc.
 | 
						|
// SPDX-License-Identifier: MPL-2.0
 | 
						|
 | 
						|
package ssh
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"encoding/base64"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/hashicorp/vault/api"
 | 
						|
	"github.com/hashicorp/vault/builtin/credential/userpass"
 | 
						|
	"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
 | 
						|
	logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
 | 
						|
	vaulthttp "github.com/hashicorp/vault/http"
 | 
						|
	"github.com/hashicorp/vault/sdk/helper/docker"
 | 
						|
	"github.com/hashicorp/vault/sdk/logical"
 | 
						|
	"github.com/hashicorp/vault/vault"
 | 
						|
	"github.com/mitchellh/mapstructure"
 | 
						|
	"github.com/stretchr/testify/require"
 | 
						|
	"golang.org/x/crypto/ssh"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	testIP            = "127.0.0.1"
 | 
						|
	testUserName      = "vaultssh"
 | 
						|
	testMultiUserName = "vaultssh,otherssh"
 | 
						|
	testAdminUser     = "vaultssh"
 | 
						|
	testCaKeyType     = "ca"
 | 
						|
	testOTPKeyType    = "otp"
 | 
						|
	testCIDRList      = "127.0.0.1/32"
 | 
						|
	testAtRoleName    = "test@RoleName"
 | 
						|
	testOTPRoleName   = "testOTPRoleName"
 | 
						|
	// testKeyName is the name of the entry that will be written to SSHMOUNTPOINT/ssh/keys
 | 
						|
	testKeyName = "testKeyName"
 | 
						|
	// testSharedPrivateKey is the value of the entry that will be written to SSHMOUNTPOINT/ssh/keys
 | 
						|
	testSharedPrivateKey = `
 | 
						|
-----BEGIN RSA PRIVATE KEY-----
 | 
						|
MIIEogIBAAKCAQEAvYvoRcWRxqOim5VZnuM6wHCbLUeiND0yaM1tvOl+Fsrz55DG
 | 
						|
A0OZp4RGAu1Fgr46E1mzxFz1+zY4UbcEExg+u21fpa8YH8sytSWW1FyuD8ICib0A
 | 
						|
/l8slmDMw4BkkGOtSlEqgscpkpv/TWZD1NxJWkPcULk8z6c7TOETn2/H9mL+v2RE
 | 
						|
mbE6NDEwJKfD3MvlpIqCP7idR+86rNBAODjGOGgyUbtFLT+K01XmDRALkV3V/nh+
 | 
						|
GltyjL4c6RU4zG2iRyV5RHlJtkml+UzUMkzr4IQnkCC32CC/wmtoo/IsAprpcHVe
 | 
						|
nkBn3eFQ7uND70p5n6GhN/KOh2j519JFHJyokwIDAQABAoIBAHX7VOvBC3kCN9/x
 | 
						|
+aPdup84OE7Z7MvpX6w+WlUhXVugnmsAAVDczhKoUc/WktLLx2huCGhsmKvyVuH+
 | 
						|
MioUiE+vx75gm3qGx5xbtmOfALVMRLopjCnJYf6EaFA0ZeQ+NwowNW7Lu0PHmAU8
 | 
						|
Z3JiX8IwxTz14DU82buDyewO7v+cEr97AnERe3PUcSTDoUXNaoNxjNpEJkKREY6h
 | 
						|
4hAY676RT/GsRcQ8tqe/rnCqPHNd7JGqL+207FK4tJw7daoBjQyijWuB7K5chSal
 | 
						|
oPInylM6b13ASXuOAOT/2uSUBWmFVCZPDCmnZxy2SdnJGbsJAMl7Ma3MUlaGvVI+
 | 
						|
Tfh1aQkCgYEA4JlNOabTb3z42wz6mz+Nz3JRwbawD+PJXOk5JsSnV7DtPtfgkK9y
 | 
						|
6FTQdhnozGWShAvJvc+C4QAihs9AlHXoaBY5bEU7R/8UK/pSqwzam+MmxmhVDV7G
 | 
						|
IMQPV0FteoXTaJSikhZ88mETTegI2mik+zleBpVxvfdhE5TR+lq8Br0CgYEA2AwJ
 | 
						|
CUD5CYUSj09PluR0HHqamWOrJkKPFPwa+5eiTTCzfBBxImYZh7nXnWuoviXC0sg2
 | 
						|
AuvCW+uZ48ygv/D8gcz3j1JfbErKZJuV+TotK9rRtNIF5Ub7qysP7UjyI7zCssVM
 | 
						|
kuDd9LfRXaB/qGAHNkcDA8NxmHW3gpln4CFdSY8CgYANs4xwfercHEWaJ1qKagAe
 | 
						|
rZyrMpffAEhicJ/Z65lB0jtG4CiE6w8ZeUMWUVJQVcnwYD+4YpZbX4S7sJ0B8Ydy
 | 
						|
AhkSr86D/92dKTIt2STk6aCN7gNyQ1vW198PtaAWH1/cO2UHgHOy3ZUt5X/Uwxl9
 | 
						|
cex4flln+1Viumts2GgsCQKBgCJH7psgSyPekK5auFdKEr5+Gc/jB8I/Z3K9+g4X
 | 
						|
5nH3G1PBTCJYLw7hRzw8W/8oALzvddqKzEFHphiGXK94Lqjt/A4q1OdbCrhiE68D
 | 
						|
My21P/dAKB1UYRSs9Y8CNyHCjuZM9jSMJ8vv6vG/SOJPsnVDWVAckAbQDvlTHC9t
 | 
						|
O98zAoGAcbW6uFDkrv0XMCpB9Su3KaNXOR0wzag+WIFQRXCcoTvxVi9iYfUReQPi
 | 
						|
oOyBJU/HMVvBfv4g+OVFLVgSwwm6owwsouZ0+D/LasbuHqYyqYqdyPJQYzWA2Y+F
 | 
						|
+B6f4RoPdSXj24JHPg/ioRxjaj094UXJxua2yfkcecGNEuBQHSs=
 | 
						|
-----END RSA PRIVATE KEY-----
 | 
						|
`
 | 
						|
	// Public half of `testCAPrivateKey`, identical to how it would be fed in from a file
 | 
						|
	testCAPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDArgK0ilRRfk8E7HIsjz5l3BuxmwpDd8DHRCVfOhbZ4gOSVxjEOOqBwWGjygdboBIZwFXmwDlU6sWX0hBJAgpQz0Cjvbjxtq/NjkvATrYPgnrXUhTaEn2eQO0PsqRNSFH46SK/oJfTp0q8/WgojxWJ2L7FUV8PO8uIk49DzqAqPV7WXU63vFsjx+3WQOX/ILeQvHCvaqs3dWjjzEoDudRWCOdUqcHEOshV9azIzPrXlQVzRV3QAKl6u7pC+/Secorpwt6IHpMKoVPGiR0tMMuNOVH8zrAKzIxPGfy2WmNDpJopbXMTvSOGAqNcp49O4SKOQl9Fzfq2HEevJamKLrMB dummy@example.com
 | 
						|
`
 | 
						|
	publicKey2 = `AAAAB3NzaC1yc2EAAAADAQABAAABAQDArgK0ilRRfk8E7HIsjz5l3BuxmwpDd8DHRCVfOhbZ4gOSVxjEOOqBwWGjygdboBIZwFXmwDlU6sWX0hBJAgpQz0Cjvbjxtq/NjkvATrYPgnrXUhTaEn2eQO0PsqRNSFH46SK/oJfTp0q8/WgojxWJ2L7FUV8PO8uIk49DzqAqPV7WXU63vFsjx+3WQOX/ILeQvHCvaqs3dWjjzEoDudRWCOdUqcHEOshV9azIzPrXlQVzRV3QAKl6u7pC+/Secorpwt6IHpMKoVPGiR0tMMuNOVH8zrAKzIxPGfy2WmNDpJopbXMTvSOGAqNcp49O4SKOQl9Fzfq2HEevJamKLrMB
 | 
						|
`
 | 
						|
 | 
						|
	publicKey3072 = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDlsMr3K1d0nzE1TjUULPRuVjEGETmOqHtWq4gVPq3HiuNVHE/e/BJnkXc40BoClQ2Z5ZZPJZ6izF9PnlzNDjpq8DrILUrn/6KrzCHvRwnkYMAXbfM/Br09z5QGptbOe1EMLeVe0b/udmUicbYAGPxMruZk+ljyr4vXkO+gOAIrxeSIQSdMVLU4g0pCPQuDCOx5IQpDYSlOB3091frpN8npfMueKPflNYzxnqqYgAVeDKAIqMCGOMOHUeIZJ7A7HuynEAVOsOkJwC9nesy9D6ppdWNduGl42IkzlwVdDMZtUAEznMUT/dnHNG1Krx9SuNZ/S9fGjxGVsT+jzUmizrWB9/6XIEHDxPBzcqlWFuwYTGz1OL8bfZ+HldOGPcnqZn9hKntWwjUc3whcvWt+NCmXpHSVLSxf+WN8pdmfEsCqn8mpvo2MXa+iJrtAVPX4i0u8AQUuqC3NuXHv4Cn0LNwtziBT544UjgbWkAZqzFZJREYA09OHscc3akEIrTnPehk= demo@example.com`
 | 
						|
 | 
						|
	publicKey4096 = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC54Oj4YCFDYxYv69Q9KfU6rWYtUB1eByQdUW0nXFi/vr98QUIV77sEeUVhaQzZcuCojAi/GrloW7ta0Z2DaEv5jOQMAnGpXBcqLJsz3KdrHbpvl93MPNdmNaGPU0GnUEsjBVuDVn9HdIUa8CNrxShvPu7/VqoaRHKLqphGgzFb37vi4qvnQ+5VYAO/TzyVYMD6qJX6I/9Pw8d74jCfEdOh2yGKkP7rXWOghreyIl8H2zTJKg9KoZuPq9F5M8nNt7Oi3rf+DwQiYvamzIqlDP4s5oFVTZW0E9lwWvYDpyiJnUrkQqksebBK/rcyfiFG3onb4qLo2WVWXeK3si8IhGik/TEzprScyAWIf9RviT8O+l5hTA2/c+ctn3MVCLRNfez2lKpdxCoprv1MbIcySGWblTJEcY6RA+aauVJpu7FMtRxHHtZKtMpep8cLu8GKbiP6Ifq2JXBtXtNxDeIgo2MkNoMh/NHAsACJniE/dqV/+u9HvhvgrTbJ69ell0nE4ivzA7O4kZgbR/4MHlLgLFvaqC8RrWRLY6BdFagPIMxghWha7Qw16zqoIjRnolvRzUWvSXanJVg8Z6ua1VxwgirNaAH1ivmJhUh2+4lNxCX6jmZyR3zjJsWY03gjJTairvI762opjjalF8fH6Xrs15mB14JiAlNbk6+5REQcvXlGqw== dummy@example.com`
 | 
						|
 | 
						|
	testCAPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
 | 
						|
MIIEowIBAAKCAQEAwK4CtIpUUX5PBOxyLI8+ZdwbsZsKQ3fAx0QlXzoW2eIDklcY
 | 
						|
xDjqgcFho8oHW6ASGcBV5sA5VOrFl9IQSQIKUM9Ao7248bavzY5LwE62D4J611IU
 | 
						|
2hJ9nkDtD7KkTUhR+Okiv6CX06dKvP1oKI8Vidi+xVFfDzvLiJOPQ86gKj1e1l1O
 | 
						|
t7xbI8ft1kDl/yC3kLxwr2qrN3Vo48xKA7nUVgjnVKnBxDrIVfWsyMz615UFc0Vd
 | 
						|
0ACperu6Qvv0nnKK6cLeiB6TCqFTxokdLTDLjTlR/M6wCsyMTxn8tlpjQ6SaKW1z
 | 
						|
E70jhgKjXKePTuEijkJfRc36thxHryWpii6zAQIDAQABAoIBAA/DrPD8iF2KigiL
 | 
						|
F+RRa/eFhLaJStOuTpV/G9eotwnolgY5Hguf5H/tRIHUG7oBZLm6pMyWWZp7AuOj
 | 
						|
CjYO9q0Z5939vc349nVI+SWoyviF4msPiik1bhWulja8lPjFu/8zg+ZNy15Dx7ei
 | 
						|
vAzleAupMiKOv8pNSB/KguQ3WZ9a9bcQcoFQ2Foru6mXpLJ03kghVRlkqvQ7t5cA
 | 
						|
n11d2Hiipq9mleESr0c+MUPKLBX/neaWfGA4xgJTjIYjZi6avmYc/Ox3sQ9aLq2J
 | 
						|
tH0D4HVUZvaU28hn+jhbs64rRFbu++qQMe3vNvi/Q/iqcYU4b6tgDNzm/JFRTS/W
 | 
						|
njiz4mkCgYEA44CnQVmonN6qQ0AgNNlBY5+RX3wwBJZ1AaxpzwDRylAt2vlVUA0n
 | 
						|
YY4RW4J4+RMRKwHwjxK5RRmHjsIJx+nrpqihW3fte3ev5F2A9Wha4dzzEHxBY6IL
 | 
						|
362T/x2f+vYk6tV+uTZSUPHsuELH26mitbBVFNB/00nbMNdEc2bO5FMCgYEA2NCw
 | 
						|
ubt+g2bRkkT/Qf8gIM8ZDpZbARt6onqxVcWkQFT16ZjbsBWUrH1Xi7alv9+lwYLJ
 | 
						|
ckY/XDX4KeU19HabeAbpyy6G9Q2uBSWZlJbjl7QNhdLeuzV82U1/r8fy6Uu3gQnU
 | 
						|
WSFx2GesRpSmZpqNKMs5ksqteZ9Yjg1EIgXdINsCgYBIn9REt1NtKGOf7kOZu1T1
 | 
						|
cYXdvm4xuLoHW7u3OiK+e9P3mCqU0G4m5UxDMyZdFKohWZAqjCaamWi9uNGYgOMa
 | 
						|
I7DG20TzaiS7OOIm9TY17eul8pSJMrypnealxRZB7fug/6Bhjaa/cktIEwFr7P4l
 | 
						|
E/JFH73+fBA9yipu0H3xQwKBgHmiwrLAZF6VrVcxDD9bQQwHA5iyc4Wwg+Fpkdl7
 | 
						|
0wUgZQHTdtRXlxwaCaZhJqX5c4WXuSo6DMvPn1TpuZZXgCsbPch2ZtJOBWXvzTSW
 | 
						|
XkK6iaedQMWoYU2L8+mK9FU73EwxVodWgwcUSosiVCRV6oGLWdZnjGEiK00uVh38
 | 
						|
Si1nAoGBAL47wWinv1cDTnh5mm0mybz3oI2a6V9aIYCloQ/EFcvtahyR/gyB8qNF
 | 
						|
lObH9Faf0WGdnACZvTz22U9gWhw79S0SpDV31tC5Kl8dXHFiZ09vYUKkYmSd/kms
 | 
						|
SeKWrUkryx46LVf6NMhkyYmRqCEjBwfOozzezi5WbiJy6nn54GQt
 | 
						|
-----END RSA PRIVATE KEY-----
 | 
						|
`
 | 
						|
 | 
						|
	testCAPublicKeyEd25519 = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1S6g5Bib7vT8eoFnvTl3dZSjOQL/GkH1nkRcDS9++a ca
 | 
						|
`
 | 
						|
 | 
						|
	testCAPrivateKeyEd25519 = `-----BEGIN OPENSSH PRIVATE KEY-----
 | 
						|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
 | 
						|
QyNTUxOQAAACDtUuoOQYm+70/HqBZ705d3WUozkC/xpB9Z5EXA0vfvmgAAAIhfRuszX0br
 | 
						|
MwAAAAtzc2gtZWQyNTUxOQAAACDtUuoOQYm+70/HqBZ705d3WUozkC/xpB9Z5EXA0vfvmg
 | 
						|
AAAEBQYa029SP/7AGPFQLmzwOc9eCoOZuwCq3iIf2C6fj9j+1S6g5Bib7vT8eoFnvTl3dZ
 | 
						|
SjOQL/GkH1nkRcDS9++aAAAAAmNhAQID
 | 
						|
-----END OPENSSH PRIVATE KEY-----
 | 
						|
`
 | 
						|
 | 
						|
	publicKeyECDSA256 = `ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJsfOouYIjJNI23QJqaDsFTGukm21fRAMeGvKZDB59i5jnX1EubMH1AEjjzz4fgySUlyWKo+TS31rxU8kX3DDM4= demo@example.com`
 | 
						|
	publicKeyECDSA521 = `ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAEg73ORD4J3FV2CrL01gLSKREO2EHrZPlJCOeDL5OKD3M1GCHv3q8O452RW49Aw+8zFFFU5u6d1Ys3Qsj05zdaQwQDt/D3ceWLGVkWiKyLPQStfn0GGOZh3YFKEw5XmeW9jh6xudEHlKs4Pfv2FrroaUKZvM2SlxR/feOK0tCQyq3MN/g== demo@example.com`
 | 
						|
 | 
						|
	// testPublicKeyInstall is the public key that is installed in the
 | 
						|
	// admin account's authorized_keys
 | 
						|
	testPublicKeyInstall = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9i+hFxZHGo6KblVme4zrAcJstR6I0PTJozW286X4WyvPnkMYDQ5mnhEYC7UWCvjoTWbPEXPX7NjhRtwQTGD67bV+lrxgfyzK1JZbUXK4PwgKJvQD+XyyWYMzDgGSQY61KUSqCxymSm/9NZkPU3ElaQ9xQuTzPpztM4ROfb8f2Yv6/ZESZsTo0MTAkp8Pcy+WkioI/uJ1H7zqs0EA4OMY4aDJRu0UtP4rTVeYNEAuRXdX+eH4aW3KMvhzpFTjMbaJHJXlEeUm2SaX5TNQyTOvghCeQILfYIL/Ca2ij8iwCmulwdV6eQGfd4VDu40PvSnmfoaE38o6HaPnX0kUcnKiT"
 | 
						|
 | 
						|
	dockerImageTagSupportsRSA1   = "8.1_p1-r0-ls20"
 | 
						|
	dockerImageTagSupportsNoRSA1 = "8.4_p1-r3-ls48"
 | 
						|
)
 | 
						|
 | 
						|
var ctx = context.Background()
 | 
						|
 | 
						|
func prepareTestContainer(t *testing.T, tag, caPublicKeyPEM string) (func(), string) {
 | 
						|
	if tag == "" {
 | 
						|
		tag = dockerImageTagSupportsNoRSA1
 | 
						|
	}
 | 
						|
	runner, err := docker.NewServiceRunner(docker.RunOptions{
 | 
						|
		ContainerName: "openssh",
 | 
						|
		ImageRepo:     "docker.mirror.hashicorp.services/linuxserver/openssh-server",
 | 
						|
		ImageTag:      tag,
 | 
						|
		Env: []string{
 | 
						|
			"DOCKER_MODS=linuxserver/mods:openssh-server-openssh-client",
 | 
						|
			"PUBLIC_KEY=" + testPublicKeyInstall,
 | 
						|
			"SUDO_ACCESS=true",
 | 
						|
			"USER_NAME=vaultssh",
 | 
						|
		},
 | 
						|
		Ports: []string{"2222/tcp"},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Could not start local ssh docker container: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	svc, err := runner.StartService(context.Background(), func(ctx context.Context, host string, port int) (docker.ServiceConfig, error) {
 | 
						|
		ipaddr, err := net.ResolveIPAddr("ip", host)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		sshAddress := fmt.Sprintf("%s:%d", ipaddr.String(), port)
 | 
						|
 | 
						|
		signer, err := ssh.ParsePrivateKey([]byte(testSharedPrivateKey))
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		// Install util-linux for non-busybox flock that supports timeout option
 | 
						|
		err = testSSH("vaultssh", sshAddress, ssh.PublicKeys(signer), fmt.Sprintf(`
 | 
						|
			set -e;
 | 
						|
			sudo ln -s /config /home/vaultssh
 | 
						|
			sudo apk add util-linux;
 | 
						|
			echo "LogLevel DEBUG" | sudo tee -a /config/ssh_host_keys/sshd_config;
 | 
						|
			echo "TrustedUserCAKeys /config/ssh_host_keys/trusted-user-ca-keys.pem" | sudo tee -a /config/ssh_host_keys/sshd_config;
 | 
						|
			kill -HUP $(cat /config/sshd.pid)
 | 
						|
			echo "%s" | sudo tee /config/ssh_host_keys/trusted-user-ca-keys.pem
 | 
						|
		`, caPublicKeyPEM))
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		return docker.NewServiceHostPort(ipaddr.String(), port), nil
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Could not start docker ssh server: %s", err)
 | 
						|
	}
 | 
						|
	return svc.Cleanup, svc.Config.Address()
 | 
						|
}
 | 
						|
 | 
						|
func testSSH(user, host string, auth ssh.AuthMethod, command string) error {
 | 
						|
	client, err := ssh.Dial("tcp", host, &ssh.ClientConfig{
 | 
						|
		User:            user,
 | 
						|
		Auth:            []ssh.AuthMethod{auth},
 | 
						|
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 | 
						|
		Timeout:         5 * time.Second,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("unable to dial sshd to host %q: %v", host, err)
 | 
						|
	}
 | 
						|
	session, err := client.NewSession()
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("unable to create sshd session to host %q: %v", host, err)
 | 
						|
	}
 | 
						|
	var stderr bytes.Buffer
 | 
						|
	session.Stderr = &stderr
 | 
						|
	defer session.Close()
 | 
						|
	err = session.Run(command)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("command %v failed, error: %v, stderr: %v", command, err, stderr.String())
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_AllowedUsers(t *testing.T) {
 | 
						|
	config := logical.TestBackendConfig()
 | 
						|
	config.StorageView = &logical.InmemStorage{}
 | 
						|
 | 
						|
	b, err := Backend(config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	err = b.Setup(context.Background(), config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	roleData := map[string]interface{}{
 | 
						|
		"key_type":      "otp",
 | 
						|
		"default_user":  "ubuntu",
 | 
						|
		"cidr_list":     "52.207.235.245/16",
 | 
						|
		"allowed_users": "test",
 | 
						|
	}
 | 
						|
 | 
						|
	roleReq := &logical.Request{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Path:      "roles/role1",
 | 
						|
		Storage:   config.StorageView,
 | 
						|
		Data:      roleData,
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err := b.HandleRequest(context.Background(), roleReq)
 | 
						|
	if err != nil || (resp != nil && resp.IsError()) || resp != nil {
 | 
						|
		t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
 | 
						|
	}
 | 
						|
 | 
						|
	credsData := map[string]interface{}{
 | 
						|
		"ip":       "52.207.235.245",
 | 
						|
		"username": "ubuntu",
 | 
						|
	}
 | 
						|
	credsReq := &logical.Request{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Storage:   config.StorageView,
 | 
						|
		Path:      "creds/role1",
 | 
						|
		Data:      credsData,
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err = b.HandleRequest(context.Background(), credsReq)
 | 
						|
	if err != nil || (resp != nil && resp.IsError()) || resp == nil {
 | 
						|
		t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
 | 
						|
	}
 | 
						|
	if resp.Data["key"] == "" ||
 | 
						|
		resp.Data["key_type"] != "otp" ||
 | 
						|
		resp.Data["ip"] != "52.207.235.245" ||
 | 
						|
		resp.Data["username"] != "ubuntu" {
 | 
						|
		t.Fatalf("failed to create credential: resp:%#v", resp)
 | 
						|
	}
 | 
						|
 | 
						|
	credsData["username"] = "test"
 | 
						|
	resp, err = b.HandleRequest(context.Background(), credsReq)
 | 
						|
	if err != nil || (resp != nil && resp.IsError()) || resp == nil {
 | 
						|
		t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
 | 
						|
	}
 | 
						|
	if resp.Data["key"] == "" ||
 | 
						|
		resp.Data["key_type"] != "otp" ||
 | 
						|
		resp.Data["ip"] != "52.207.235.245" ||
 | 
						|
		resp.Data["username"] != "test" {
 | 
						|
		t.Fatalf("failed to create credential: resp:%#v", resp)
 | 
						|
	}
 | 
						|
 | 
						|
	credsData["username"] = "random"
 | 
						|
	resp, err = b.HandleRequest(context.Background(), credsReq)
 | 
						|
	if err != nil || resp == nil || (resp != nil && !resp.IsError()) {
 | 
						|
		t.Fatalf("expected failure: resp:%#v err:%s", resp, err)
 | 
						|
	}
 | 
						|
 | 
						|
	delete(roleData, "allowed_users")
 | 
						|
	resp, err = b.HandleRequest(context.Background(), roleReq)
 | 
						|
	if err != nil || (resp != nil && resp.IsError()) || resp != nil {
 | 
						|
		t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
 | 
						|
	}
 | 
						|
 | 
						|
	credsData["username"] = "ubuntu"
 | 
						|
	resp, err = b.HandleRequest(context.Background(), credsReq)
 | 
						|
	if err != nil || (resp != nil && resp.IsError()) || resp == nil {
 | 
						|
		t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
 | 
						|
	}
 | 
						|
	if resp.Data["key"] == "" ||
 | 
						|
		resp.Data["key_type"] != "otp" ||
 | 
						|
		resp.Data["ip"] != "52.207.235.245" ||
 | 
						|
		resp.Data["username"] != "ubuntu" {
 | 
						|
		t.Fatalf("failed to create credential: resp:%#v", resp)
 | 
						|
	}
 | 
						|
 | 
						|
	credsData["username"] = "test"
 | 
						|
	resp, err = b.HandleRequest(context.Background(), credsReq)
 | 
						|
	if err != nil || resp == nil || (resp != nil && !resp.IsError()) {
 | 
						|
		t.Fatalf("expected failure: resp:%#v err:%s", resp, err)
 | 
						|
	}
 | 
						|
 | 
						|
	roleData["allowed_users"] = "*"
 | 
						|
	resp, err = b.HandleRequest(context.Background(), roleReq)
 | 
						|
	if err != nil || (resp != nil && resp.IsError()) || resp != nil {
 | 
						|
		t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err = b.HandleRequest(context.Background(), credsReq)
 | 
						|
	if err != nil || (resp != nil && resp.IsError()) || resp == nil {
 | 
						|
		t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
 | 
						|
	}
 | 
						|
	if resp.Data["key"] == "" ||
 | 
						|
		resp.Data["key_type"] != "otp" ||
 | 
						|
		resp.Data["ip"] != "52.207.235.245" ||
 | 
						|
		resp.Data["username"] != "test" {
 | 
						|
		t.Fatalf("failed to create credential: resp:%#v", resp)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_AllowedDomainsTemplate(t *testing.T) {
 | 
						|
	testAllowedDomainsTemplate := "{{ identity.entity.metadata.ssh_username }}.example.com"
 | 
						|
	expectedValidPrincipal := "foo." + testUserName + ".example.com"
 | 
						|
	testAllowedPrincipalsTemplate(
 | 
						|
		t, testAllowedDomainsTemplate,
 | 
						|
		expectedValidPrincipal,
 | 
						|
		map[string]string{
 | 
						|
			"ssh_username": testUserName,
 | 
						|
		},
 | 
						|
		map[string]interface{}{
 | 
						|
			"key_type":                 testCaKeyType,
 | 
						|
			"algorithm_signer":         "rsa-sha2-256",
 | 
						|
			"allow_host_certificates":  true,
 | 
						|
			"allow_subdomains":         true,
 | 
						|
			"allowed_domains":          testAllowedDomainsTemplate,
 | 
						|
			"allowed_domains_template": true,
 | 
						|
		},
 | 
						|
		map[string]interface{}{
 | 
						|
			"cert_type":        "host",
 | 
						|
			"public_key":       testCAPublicKey,
 | 
						|
			"valid_principals": expectedValidPrincipal,
 | 
						|
		},
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_AllowedUsersTemplate(t *testing.T) {
 | 
						|
	testAllowedUsersTemplate(t,
 | 
						|
		"{{ identity.entity.metadata.ssh_username }}",
 | 
						|
		testUserName, map[string]string{
 | 
						|
			"ssh_username": testUserName,
 | 
						|
		},
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_MultipleAllowedUsersTemplate(t *testing.T) {
 | 
						|
	testAllowedUsersTemplate(t,
 | 
						|
		"{{ identity.entity.metadata.ssh_username }}",
 | 
						|
		testUserName, map[string]string{
 | 
						|
			"ssh_username": testMultiUserName,
 | 
						|
		},
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_AllowedUsersTemplate_WithStaticPrefix(t *testing.T) {
 | 
						|
	testAllowedUsersTemplate(t,
 | 
						|
		"ssh-{{ identity.entity.metadata.ssh_username }}",
 | 
						|
		"ssh-"+testUserName, map[string]string{
 | 
						|
			"ssh_username": testUserName,
 | 
						|
		},
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_DefaultUserTemplate(t *testing.T) {
 | 
						|
	testDefaultUserTemplate(t,
 | 
						|
		"{{ identity.entity.metadata.ssh_username }}",
 | 
						|
		testUserName,
 | 
						|
		map[string]string{
 | 
						|
			"ssh_username": testUserName,
 | 
						|
		},
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_DefaultUserTemplate_WithStaticPrefix(t *testing.T) {
 | 
						|
	testDefaultUserTemplate(t,
 | 
						|
		"user-{{ identity.entity.metadata.ssh_username }}",
 | 
						|
		"user-"+testUserName,
 | 
						|
		map[string]string{
 | 
						|
			"ssh_username": testUserName,
 | 
						|
		},
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_DefaultUserTemplateFalse_AllowedUsersTemplateTrue(t *testing.T) {
 | 
						|
	cluster, userpassToken := getSshCaTestCluster(t, testUserName)
 | 
						|
	defer cluster.Cleanup()
 | 
						|
	client := cluster.Cores[0].Client
 | 
						|
 | 
						|
	// set metadata "ssh_username" to userpass username
 | 
						|
	tokenLookupResponse, err := client.Logical().Write("/auth/token/lookup", map[string]interface{}{
 | 
						|
		"token": userpassToken,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	entityID := tokenLookupResponse.Data["entity_id"].(string)
 | 
						|
	_, err = client.Logical().Write("/identity/entity/id/"+entityID, map[string]interface{}{
 | 
						|
		"metadata": map[string]string{
 | 
						|
			"ssh_username": testUserName,
 | 
						|
		},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = client.Logical().Write("ssh/roles/my-role", map[string]interface{}{
 | 
						|
		"key_type":                testCaKeyType,
 | 
						|
		"allow_user_certificates": true,
 | 
						|
		"default_user":            "{{identity.entity.metadata.ssh_username}}",
 | 
						|
		// disable user templating but not allowed_user_template and the request should fail
 | 
						|
		"default_user_template":  false,
 | 
						|
		"allowed_users":          "{{identity.entity.metadata.ssh_username}}",
 | 
						|
		"allowed_users_template": true,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// sign SSH key as userpass user
 | 
						|
	client.SetToken(userpassToken)
 | 
						|
	_, err = client.Logical().Write("ssh/sign/my-role", map[string]interface{}{
 | 
						|
		"public_key": testCAPublicKey,
 | 
						|
	})
 | 
						|
	if err == nil {
 | 
						|
		t.Errorf("signing request should fail when default_user is not in the allowed_users list, because allowed_users_template is true and default_user_template is not")
 | 
						|
	}
 | 
						|
 | 
						|
	expectedErrStr := "{{identity.entity.metadata.ssh_username}} is not a valid value for valid_principals"
 | 
						|
	if !strings.Contains(err.Error(), expectedErrStr) {
 | 
						|
		t.Errorf("expected error to include %q but it was: %q", expectedErrStr, err.Error())
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_DefaultUserTemplateFalse_AllowedUsersTemplateFalse(t *testing.T) {
 | 
						|
	cluster, userpassToken := getSshCaTestCluster(t, testUserName)
 | 
						|
	defer cluster.Cleanup()
 | 
						|
	client := cluster.Cores[0].Client
 | 
						|
 | 
						|
	// set metadata "ssh_username" to userpass username
 | 
						|
	tokenLookupResponse, err := client.Logical().Write("/auth/token/lookup", map[string]interface{}{
 | 
						|
		"token": userpassToken,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	entityID := tokenLookupResponse.Data["entity_id"].(string)
 | 
						|
	_, err = client.Logical().Write("/identity/entity/id/"+entityID, map[string]interface{}{
 | 
						|
		"metadata": map[string]string{
 | 
						|
			"ssh_username": testUserName,
 | 
						|
		},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = client.Logical().Write("ssh/roles/my-role", map[string]interface{}{
 | 
						|
		"key_type":                testCaKeyType,
 | 
						|
		"allow_user_certificates": true,
 | 
						|
		"default_user":            "{{identity.entity.metadata.ssh_username}}",
 | 
						|
		"default_user_template":   false,
 | 
						|
		"allowed_users":           "{{identity.entity.metadata.ssh_username}}",
 | 
						|
		"allowed_users_template":  false,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// sign SSH key as userpass user
 | 
						|
	client.SetToken(userpassToken)
 | 
						|
	signResponse, err := client.Logical().Write("ssh/sign/my-role", map[string]interface{}{
 | 
						|
		"public_key": testCAPublicKey,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// check for the expected valid principals of certificate
 | 
						|
	signedKey := signResponse.Data["signed_key"].(string)
 | 
						|
	key, _ := base64.StdEncoding.DecodeString(strings.Split(signedKey, " ")[1])
 | 
						|
	parsedKey, err := ssh.ParsePublicKey(key)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	actualPrincipals := parsedKey.(*ssh.Certificate).ValidPrincipals
 | 
						|
	if len(actualPrincipals) < 1 {
 | 
						|
		t.Fatal(
 | 
						|
			fmt.Sprintf("No ValidPrincipals returned: should have been %v",
 | 
						|
				[]string{"{{identity.entity.metadata.ssh_username}}"}),
 | 
						|
		)
 | 
						|
	}
 | 
						|
	if len(actualPrincipals) > 1 {
 | 
						|
		t.Error(
 | 
						|
			fmt.Sprintf("incorrect number ValidPrincipals, expected only 1: %v should be %v",
 | 
						|
				actualPrincipals, []string{"{{identity.entity.metadata.ssh_username}}"}),
 | 
						|
		)
 | 
						|
	}
 | 
						|
	if actualPrincipals[0] != "{{identity.entity.metadata.ssh_username}}" {
 | 
						|
		t.Fatal(
 | 
						|
			fmt.Sprintf("incorrect ValidPrincipals: %v should be %v",
 | 
						|
				actualPrincipals, []string{"{{identity.entity.metadata.ssh_username}}"}),
 | 
						|
		)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func newTestingFactory(t *testing.T) func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
 | 
						|
	return func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
 | 
						|
		defaultLeaseTTLVal := 2 * time.Minute
 | 
						|
		maxLeaseTTLVal := 10 * time.Minute
 | 
						|
		return Factory(context.Background(), &logical.BackendConfig{
 | 
						|
			Logger:      corehelpers.NewTestLogger(t),
 | 
						|
			StorageView: &logical.InmemStorage{},
 | 
						|
			System: &logical.StaticSystemView{
 | 
						|
				DefaultLeaseTTLVal: defaultLeaseTTLVal,
 | 
						|
				MaxLeaseTTLVal:     maxLeaseTTLVal,
 | 
						|
			},
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSSHBackend_Lookup(t *testing.T) {
 | 
						|
	testOTPRoleData := map[string]interface{}{
 | 
						|
		"key_type":     testOTPKeyType,
 | 
						|
		"default_user": testUserName,
 | 
						|
		"cidr_list":    testCIDRList,
 | 
						|
	}
 | 
						|
	data := map[string]interface{}{
 | 
						|
		"ip": testIP,
 | 
						|
	}
 | 
						|
	resp1 := []string(nil)
 | 
						|
	resp2 := []string{testOTPRoleName}
 | 
						|
	resp3 := []string{testAtRoleName}
 | 
						|
	logicaltest.Test(t, logicaltest.TestCase{
 | 
						|
		LogicalFactory: newTestingFactory(t),
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			testLookupRead(t, data, resp1),
 | 
						|
			testRoleWrite(t, testOTPRoleName, testOTPRoleData),
 | 
						|
			testLookupRead(t, data, resp2),
 | 
						|
			testRoleDelete(t, testOTPRoleName),
 | 
						|
			testLookupRead(t, data, resp1),
 | 
						|
			testRoleWrite(t, testAtRoleName, testOTPRoleData),
 | 
						|
			testLookupRead(t, data, resp3),
 | 
						|
			testRoleDelete(t, testAtRoleName),
 | 
						|
			testLookupRead(t, data, resp1),
 | 
						|
		},
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSSHBackend_RoleList(t *testing.T) {
 | 
						|
	testOTPRoleData := map[string]interface{}{
 | 
						|
		"key_type":     testOTPKeyType,
 | 
						|
		"default_user": testUserName,
 | 
						|
		"cidr_list":    testCIDRList,
 | 
						|
	}
 | 
						|
	resp1 := map[string]interface{}{}
 | 
						|
	resp2 := map[string]interface{}{
 | 
						|
		"keys": []string{testOTPRoleName},
 | 
						|
		"key_info": map[string]interface{}{
 | 
						|
			testOTPRoleName: map[string]interface{}{
 | 
						|
				"key_type": testOTPKeyType,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	resp3 := map[string]interface{}{
 | 
						|
		"keys": []string{testAtRoleName, testOTPRoleName},
 | 
						|
		"key_info": map[string]interface{}{
 | 
						|
			testOTPRoleName: map[string]interface{}{
 | 
						|
				"key_type": testOTPKeyType,
 | 
						|
			},
 | 
						|
			testAtRoleName: map[string]interface{}{
 | 
						|
				"key_type": testOTPKeyType,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	logicaltest.Test(t, logicaltest.TestCase{
 | 
						|
		LogicalFactory: newTestingFactory(t),
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			testRoleList(t, resp1),
 | 
						|
			testRoleWrite(t, testOTPRoleName, testOTPRoleData),
 | 
						|
			testRoleList(t, resp2),
 | 
						|
			testRoleWrite(t, testAtRoleName, testOTPRoleData),
 | 
						|
			testRoleList(t, resp3),
 | 
						|
			testRoleDelete(t, testAtRoleName),
 | 
						|
			testRoleList(t, resp2),
 | 
						|
			testRoleDelete(t, testOTPRoleName),
 | 
						|
			testRoleList(t, resp1),
 | 
						|
		},
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSSHBackend_OTPRoleCrud(t *testing.T) {
 | 
						|
	testOTPRoleData := map[string]interface{}{
 | 
						|
		"key_type":     testOTPKeyType,
 | 
						|
		"default_user": testUserName,
 | 
						|
		"cidr_list":    testCIDRList,
 | 
						|
	}
 | 
						|
	respOTPRoleData := map[string]interface{}{
 | 
						|
		"key_type":     testOTPKeyType,
 | 
						|
		"port":         22,
 | 
						|
		"default_user": testUserName,
 | 
						|
		"cidr_list":    testCIDRList,
 | 
						|
	}
 | 
						|
	logicaltest.Test(t, logicaltest.TestCase{
 | 
						|
		LogicalFactory: newTestingFactory(t),
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			testRoleWrite(t, testOTPRoleName, testOTPRoleData),
 | 
						|
			testRoleRead(t, testOTPRoleName, respOTPRoleData),
 | 
						|
			testRoleDelete(t, testOTPRoleName),
 | 
						|
			testRoleRead(t, testOTPRoleName, nil),
 | 
						|
			testRoleWrite(t, testAtRoleName, testOTPRoleData),
 | 
						|
			testRoleRead(t, testAtRoleName, respOTPRoleData),
 | 
						|
			testRoleDelete(t, testAtRoleName),
 | 
						|
			testRoleRead(t, testAtRoleName, nil),
 | 
						|
		},
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSSHBackend_OTPCreate(t *testing.T) {
 | 
						|
	cleanup, sshAddress := prepareTestContainer(t, "", "")
 | 
						|
	defer func() {
 | 
						|
		if !t.Failed() {
 | 
						|
			cleanup()
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	host, port, err := net.SplitHostPort(sshAddress)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	testOTPRoleData := map[string]interface{}{
 | 
						|
		"key_type":     testOTPKeyType,
 | 
						|
		"default_user": testUserName,
 | 
						|
		"cidr_list":    host + "/32",
 | 
						|
		"port":         port,
 | 
						|
	}
 | 
						|
	data := map[string]interface{}{
 | 
						|
		"username": testUserName,
 | 
						|
		"ip":       host,
 | 
						|
	}
 | 
						|
	logicaltest.Test(t, logicaltest.TestCase{
 | 
						|
		LogicalFactory: newTestingFactory(t),
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			testRoleWrite(t, testOTPRoleName, testOTPRoleData),
 | 
						|
			testCredsWrite(t, testOTPRoleName, data, false, sshAddress),
 | 
						|
		},
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSSHBackend_VerifyEcho(t *testing.T) {
 | 
						|
	verifyData := map[string]interface{}{
 | 
						|
		"otp": api.VerifyEchoRequest,
 | 
						|
	}
 | 
						|
	expectedData := map[string]interface{}{
 | 
						|
		"message": api.VerifyEchoResponse,
 | 
						|
	}
 | 
						|
	logicaltest.Test(t, logicaltest.TestCase{
 | 
						|
		LogicalFactory: newTestingFactory(t),
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			testVerifyWrite(t, verifyData, expectedData),
 | 
						|
		},
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSSHBackend_ConfigZeroAddressCRUD(t *testing.T) {
 | 
						|
	testOTPRoleData := map[string]interface{}{
 | 
						|
		"key_type":     testOTPKeyType,
 | 
						|
		"default_user": testUserName,
 | 
						|
		"cidr_list":    testCIDRList,
 | 
						|
	}
 | 
						|
	req1 := map[string]interface{}{
 | 
						|
		"roles": testOTPRoleName,
 | 
						|
	}
 | 
						|
	resp1 := map[string]interface{}{
 | 
						|
		"roles": []string{testOTPRoleName},
 | 
						|
	}
 | 
						|
	resp2 := map[string]interface{}{
 | 
						|
		"roles": []string{testOTPRoleName},
 | 
						|
	}
 | 
						|
	resp3 := map[string]interface{}{
 | 
						|
		"roles": []string{},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, logicaltest.TestCase{
 | 
						|
		LogicalFactory: newTestingFactory(t),
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			testRoleWrite(t, testOTPRoleName, testOTPRoleData),
 | 
						|
			testConfigZeroAddressWrite(t, req1),
 | 
						|
			testConfigZeroAddressRead(t, resp1),
 | 
						|
			testConfigZeroAddressRead(t, resp2),
 | 
						|
			testConfigZeroAddressRead(t, resp1),
 | 
						|
			testRoleDelete(t, testOTPRoleName),
 | 
						|
			testConfigZeroAddressRead(t, resp3),
 | 
						|
			testConfigZeroAddressDelete(t),
 | 
						|
		},
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSSHBackend_CredsForZeroAddressRoles_otp(t *testing.T) {
 | 
						|
	otpRoleData := map[string]interface{}{
 | 
						|
		"key_type":     testOTPKeyType,
 | 
						|
		"default_user": testUserName,
 | 
						|
	}
 | 
						|
	data := map[string]interface{}{
 | 
						|
		"username": testUserName,
 | 
						|
		"ip":       testIP,
 | 
						|
	}
 | 
						|
	req1 := map[string]interface{}{
 | 
						|
		"roles": testOTPRoleName,
 | 
						|
	}
 | 
						|
	logicaltest.Test(t, logicaltest.TestCase{
 | 
						|
		LogicalFactory: newTestingFactory(t),
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			testRoleWrite(t, testOTPRoleName, otpRoleData),
 | 
						|
			testCredsWrite(t, testOTPRoleName, data, true, ""),
 | 
						|
			testConfigZeroAddressWrite(t, req1),
 | 
						|
			testCredsWrite(t, testOTPRoleName, data, false, ""),
 | 
						|
			testConfigZeroAddressDelete(t),
 | 
						|
			testCredsWrite(t, testOTPRoleName, data, true, ""),
 | 
						|
		},
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSSHBackend_CA(t *testing.T) {
 | 
						|
	testCases := []struct {
 | 
						|
		name         string
 | 
						|
		tag          string
 | 
						|
		caPublicKey  string
 | 
						|
		caPrivateKey string
 | 
						|
		algoSigner   string
 | 
						|
		expectError  bool
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			"RSAKey_EmptyAlgoSigner_ImageSupportsRSA1",
 | 
						|
			dockerImageTagSupportsRSA1,
 | 
						|
			testCAPublicKey,
 | 
						|
			testCAPrivateKey,
 | 
						|
			"",
 | 
						|
			false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"RSAKey_EmptyAlgoSigner_ImageSupportsNoRSA1",
 | 
						|
			dockerImageTagSupportsNoRSA1,
 | 
						|
			testCAPublicKey,
 | 
						|
			testCAPrivateKey,
 | 
						|
			"",
 | 
						|
			false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"RSAKey_DefaultAlgoSigner_ImageSupportsRSA1",
 | 
						|
			dockerImageTagSupportsRSA1,
 | 
						|
			testCAPublicKey,
 | 
						|
			testCAPrivateKey,
 | 
						|
			"default",
 | 
						|
			false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"RSAKey_DefaultAlgoSigner_ImageSupportsNoRSA1",
 | 
						|
			dockerImageTagSupportsNoRSA1,
 | 
						|
			testCAPublicKey,
 | 
						|
			testCAPrivateKey,
 | 
						|
			"default",
 | 
						|
			false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"RSAKey_RSA1AlgoSigner_ImageSupportsRSA1",
 | 
						|
			dockerImageTagSupportsRSA1,
 | 
						|
			testCAPublicKey,
 | 
						|
			testCAPrivateKey,
 | 
						|
			ssh.SigAlgoRSA,
 | 
						|
			false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"RSAKey_RSA1AlgoSigner_ImageSupportsNoRSA1",
 | 
						|
			dockerImageTagSupportsNoRSA1,
 | 
						|
			testCAPublicKey,
 | 
						|
			testCAPrivateKey,
 | 
						|
			ssh.SigAlgoRSA,
 | 
						|
			true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"RSAKey_RSASHA2256AlgoSigner_ImageSupportsRSA1",
 | 
						|
			dockerImageTagSupportsRSA1,
 | 
						|
			testCAPublicKey,
 | 
						|
			testCAPrivateKey,
 | 
						|
			ssh.SigAlgoRSASHA2256,
 | 
						|
			false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"RSAKey_RSASHA2256AlgoSigner_ImageSupportsNoRSA1",
 | 
						|
			dockerImageTagSupportsNoRSA1,
 | 
						|
			testCAPublicKey,
 | 
						|
			testCAPrivateKey,
 | 
						|
			ssh.SigAlgoRSASHA2256,
 | 
						|
			false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"ed25519Key_EmptyAlgoSigner_ImageSupportsRSA1",
 | 
						|
			dockerImageTagSupportsRSA1,
 | 
						|
			testCAPublicKeyEd25519,
 | 
						|
			testCAPrivateKeyEd25519,
 | 
						|
			"",
 | 
						|
			false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"ed25519Key_EmptyAlgoSigner_ImageSupportsNoRSA1",
 | 
						|
			dockerImageTagSupportsNoRSA1,
 | 
						|
			testCAPublicKeyEd25519,
 | 
						|
			testCAPrivateKeyEd25519,
 | 
						|
			"",
 | 
						|
			false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"ed25519Key_RSA1AlgoSigner_ImageSupportsRSA1",
 | 
						|
			dockerImageTagSupportsRSA1,
 | 
						|
			testCAPublicKeyEd25519,
 | 
						|
			testCAPrivateKeyEd25519,
 | 
						|
			ssh.SigAlgoRSA,
 | 
						|
			true,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testCases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			testSSHBackend_CA(t, tc.tag, tc.caPublicKey, tc.caPrivateKey, tc.algoSigner, tc.expectError)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testSSHBackend_CA(t *testing.T, dockerImageTag, caPublicKey, caPrivateKey, algorithmSigner string, expectError bool) {
 | 
						|
	cleanup, sshAddress := prepareTestContainer(t, dockerImageTag, caPublicKey)
 | 
						|
	defer cleanup()
 | 
						|
	config := logical.TestBackendConfig()
 | 
						|
 | 
						|
	b, err := Factory(context.Background(), config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Cannot create backend: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	testKeyToSignPrivate := `-----BEGIN OPENSSH PRIVATE KEY-----
 | 
						|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
 | 
						|
NhAAAAAwEAAQAAAQEAwn1V2xd/EgJXIY53fBTtc20k/ajekqQngvkpFSwNHW63XNEQK8Ll
 | 
						|
FOCyGXoje9DUGxnYs3F/ohfsBBWkLNfU7fiENdSJL1pbkAgJ+2uhV9sLZjvYhikrXWoyJX
 | 
						|
LDKfY12LjpcBS2HeLMT04laZ/xSJrOBEJHGzHyr2wUO0NUQUQPUODAFhnHKgvvA4Uu79UY
 | 
						|
gcdThF4w83+EAnE4JzBZMKPMjzy4u1C0R/LoD8DuapHwX6NGWdEUvUZZ+XRcIWeCOvR0ne
 | 
						|
qGBRH35k1Mv7k65d7kkE0uvM5Z36erw3tdoszxPYf7AKnO1DpeU2uwMcym6xNwfwynKjhL
 | 
						|
qL/Mgi4uRwAAA8iAsY0zgLGNMwAAAAdzc2gtcnNhAAABAQDCfVXbF38SAlchjnd8FO1zbS
 | 
						|
T9qN6SpCeC+SkVLA0dbrdc0RArwuUU4LIZeiN70NQbGdizcX+iF+wEFaQs19Tt+IQ11Ikv
 | 
						|
WluQCAn7a6FX2wtmO9iGKStdajIlcsMp9jXYuOlwFLYd4sxPTiVpn/FIms4EQkcbMfKvbB
 | 
						|
Q7Q1RBRA9Q4MAWGccqC+8DhS7v1RiBx1OEXjDzf4QCcTgnMFkwo8yPPLi7ULRH8ugPwO5q
 | 
						|
kfBfo0ZZ0RS9Rln5dFwhZ4I69HSd6oYFEffmTUy/uTrl3uSQTS68zlnfp6vDe12izPE9h/
 | 
						|
sAqc7UOl5Ta7AxzKbrE3B/DKcqOEuov8yCLi5HAAAAAwEAAQAAAQABns2yT5XNbpuPOgKg
 | 
						|
1APObGBchKWmDxwNKUpAVOefEScR7OP3mV4TOHQDZlMZWvoJZ8O4av+nOA/NUOjXPs0VVn
 | 
						|
azhBvIezY8EvUSVSk49Cg6J9F7/KfR1WqpiTU7CkQUlCXNuz5xLUyKdJo3MQ/vjOqeenbh
 | 
						|
MR9Wes4IWF1BVe4VOD6lxRsjwuIieIgmScW28FFh2rgsEfO2spzZ3AWOGExw+ih757hFz5
 | 
						|
4A2fhsQXP8m3r8m7iiqcjTLWXdxTUk4zot2kZEjbI4Avk0BL+wVeFq6f/y+G+g5edqSo7j
 | 
						|
uuSgzbUQtA9PMnGxhrhU2Ob7n3VGdya7WbGZkaKP8zJhAAAAgQC3bJurmOSLIi3KVhp7lD
 | 
						|
/FfxwXHwVBFALCgq7EyNlkTz6RDoMFM4eOTRMDvsgWxT+bSB8R8eg1sfgY8rkHOuvTAVI5
 | 
						|
3oEYco3H7NWE9X8Zt0lyhO1uaE49EENNSQ8hY7R3UIw5becyI+7ZZxs9HkBgCQCZzSjzA+
 | 
						|
SIyAoMKM261AAAAIEA+PCkcDRp3J0PaoiuetXSlWZ5WjP3CtwT2xrvEX9x+ZsDgXCDYQ5T
 | 
						|
osxvEKOGSfIrHUUhzZbFGvqWyfrziPe9ypJrtCM7RJT/fApBXnbWFcDZzWamkQvohst+0w
 | 
						|
XHYCmNoJ6/Y+roLv3pzyFUmqRNcrQaohex7TZmsvHJT513UakAAACBAMgBXxH8DyNYdniX
 | 
						|
mIXEto4GqMh4rXdNwCghfpyWdJE6vCyDt7g7bYMq7AQ2ynSKRtQDT/ZgQNfSbilUq3iXz7
 | 
						|
xNZn5U9ndwFs90VmEpBup/PmhfX+Gwt5hQZLbkKZcgQ9XrhSKdMxVm1yy/fk0U457enlz5
 | 
						|
cKumubUxOfFdy1ZvAAAAEm5jY0BtYnAudWJudC5sb2NhbA==
 | 
						|
-----END OPENSSH PRIVATE KEY-----
 | 
						|
`
 | 
						|
	testKeyToSignPublic := `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCfVXbF38SAlchjnd8FO1zbST9qN6SpCeC+SkVLA0dbrdc0RArwuUU4LIZeiN70NQbGdizcX+iF+wEFaQs19Tt+IQ11IkvWluQCAn7a6FX2wtmO9iGKStdajIlcsMp9jXYuOlwFLYd4sxPTiVpn/FIms4EQkcbMfKvbBQ7Q1RBRA9Q4MAWGccqC+8DhS7v1RiBx1OEXjDzf4QCcTgnMFkwo8yPPLi7ULRH8ugPwO5qkfBfo0ZZ0RS9Rln5dFwhZ4I69HSd6oYFEffmTUy/uTrl3uSQTS68zlnfp6vDe12izPE9h/sAqc7UOl5Ta7AxzKbrE3B/DKcqOEuov8yCLi5H `
 | 
						|
 | 
						|
	roleOptions := map[string]interface{}{
 | 
						|
		"allow_user_certificates": true,
 | 
						|
		"allowed_users":           "*",
 | 
						|
		"default_extensions": []map[string]string{
 | 
						|
			{
 | 
						|
				"permit-pty": "",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"key_type":     "ca",
 | 
						|
		"default_user": testUserName,
 | 
						|
		"ttl":          "30m0s",
 | 
						|
	}
 | 
						|
	if algorithmSigner != "" {
 | 
						|
		roleOptions["algorithm_signer"] = algorithmSigner
 | 
						|
	}
 | 
						|
	testCase := logicaltest.TestCase{
 | 
						|
		LogicalBackend: b,
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			configCaStep(caPublicKey, caPrivateKey),
 | 
						|
			testRoleWrite(t, "testcarole", roleOptions),
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/testcarole",
 | 
						|
				ErrorOk:   expectError,
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key":       testKeyToSignPublic,
 | 
						|
					"valid_principals": testUserName,
 | 
						|
				},
 | 
						|
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					// Tolerate nil response if an error was expected
 | 
						|
					if expectError && resp == nil {
 | 
						|
						return nil
 | 
						|
					}
 | 
						|
 | 
						|
					signedKey := strings.TrimSpace(resp.Data["signed_key"].(string))
 | 
						|
					if signedKey == "" {
 | 
						|
						return errors.New("no signed key in response")
 | 
						|
					}
 | 
						|
 | 
						|
					privKey, err := ssh.ParsePrivateKey([]byte(testKeyToSignPrivate))
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("error parsing private key: %v", err)
 | 
						|
					}
 | 
						|
 | 
						|
					parsedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(signedKey))
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("error parsing signed key: %v", err)
 | 
						|
					}
 | 
						|
					certSigner, err := ssh.NewCertSigner(parsedKey.(*ssh.Certificate), privKey)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
 | 
						|
					err = testSSH(testUserName, sshAddress, ssh.PublicKeys(certSigner), "date")
 | 
						|
					if expectError && err == nil {
 | 
						|
						return fmt.Errorf("expected error but got none")
 | 
						|
					}
 | 
						|
					if !expectError && err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, testCase)
 | 
						|
}
 | 
						|
 | 
						|
func TestSSHBackend_CAUpgradeAlgorithmSigner(t *testing.T) {
 | 
						|
	cleanup, sshAddress := prepareTestContainer(t, dockerImageTagSupportsRSA1, testCAPublicKey)
 | 
						|
	defer cleanup()
 | 
						|
	config := logical.TestBackendConfig()
 | 
						|
 | 
						|
	b, err := Factory(context.Background(), config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Cannot create backend: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	testKeyToSignPrivate := `-----BEGIN OPENSSH PRIVATE KEY-----
 | 
						|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
 | 
						|
NhAAAAAwEAAQAAAQEAwn1V2xd/EgJXIY53fBTtc20k/ajekqQngvkpFSwNHW63XNEQK8Ll
 | 
						|
FOCyGXoje9DUGxnYs3F/ohfsBBWkLNfU7fiENdSJL1pbkAgJ+2uhV9sLZjvYhikrXWoyJX
 | 
						|
LDKfY12LjpcBS2HeLMT04laZ/xSJrOBEJHGzHyr2wUO0NUQUQPUODAFhnHKgvvA4Uu79UY
 | 
						|
gcdThF4w83+EAnE4JzBZMKPMjzy4u1C0R/LoD8DuapHwX6NGWdEUvUZZ+XRcIWeCOvR0ne
 | 
						|
qGBRH35k1Mv7k65d7kkE0uvM5Z36erw3tdoszxPYf7AKnO1DpeU2uwMcym6xNwfwynKjhL
 | 
						|
qL/Mgi4uRwAAA8iAsY0zgLGNMwAAAAdzc2gtcnNhAAABAQDCfVXbF38SAlchjnd8FO1zbS
 | 
						|
T9qN6SpCeC+SkVLA0dbrdc0RArwuUU4LIZeiN70NQbGdizcX+iF+wEFaQs19Tt+IQ11Ikv
 | 
						|
WluQCAn7a6FX2wtmO9iGKStdajIlcsMp9jXYuOlwFLYd4sxPTiVpn/FIms4EQkcbMfKvbB
 | 
						|
Q7Q1RBRA9Q4MAWGccqC+8DhS7v1RiBx1OEXjDzf4QCcTgnMFkwo8yPPLi7ULRH8ugPwO5q
 | 
						|
kfBfo0ZZ0RS9Rln5dFwhZ4I69HSd6oYFEffmTUy/uTrl3uSQTS68zlnfp6vDe12izPE9h/
 | 
						|
sAqc7UOl5Ta7AxzKbrE3B/DKcqOEuov8yCLi5HAAAAAwEAAQAAAQABns2yT5XNbpuPOgKg
 | 
						|
1APObGBchKWmDxwNKUpAVOefEScR7OP3mV4TOHQDZlMZWvoJZ8O4av+nOA/NUOjXPs0VVn
 | 
						|
azhBvIezY8EvUSVSk49Cg6J9F7/KfR1WqpiTU7CkQUlCXNuz5xLUyKdJo3MQ/vjOqeenbh
 | 
						|
MR9Wes4IWF1BVe4VOD6lxRsjwuIieIgmScW28FFh2rgsEfO2spzZ3AWOGExw+ih757hFz5
 | 
						|
4A2fhsQXP8m3r8m7iiqcjTLWXdxTUk4zot2kZEjbI4Avk0BL+wVeFq6f/y+G+g5edqSo7j
 | 
						|
uuSgzbUQtA9PMnGxhrhU2Ob7n3VGdya7WbGZkaKP8zJhAAAAgQC3bJurmOSLIi3KVhp7lD
 | 
						|
/FfxwXHwVBFALCgq7EyNlkTz6RDoMFM4eOTRMDvsgWxT+bSB8R8eg1sfgY8rkHOuvTAVI5
 | 
						|
3oEYco3H7NWE9X8Zt0lyhO1uaE49EENNSQ8hY7R3UIw5becyI+7ZZxs9HkBgCQCZzSjzA+
 | 
						|
SIyAoMKM261AAAAIEA+PCkcDRp3J0PaoiuetXSlWZ5WjP3CtwT2xrvEX9x+ZsDgXCDYQ5T
 | 
						|
osxvEKOGSfIrHUUhzZbFGvqWyfrziPe9ypJrtCM7RJT/fApBXnbWFcDZzWamkQvohst+0w
 | 
						|
XHYCmNoJ6/Y+roLv3pzyFUmqRNcrQaohex7TZmsvHJT513UakAAACBAMgBXxH8DyNYdniX
 | 
						|
mIXEto4GqMh4rXdNwCghfpyWdJE6vCyDt7g7bYMq7AQ2ynSKRtQDT/ZgQNfSbilUq3iXz7
 | 
						|
xNZn5U9ndwFs90VmEpBup/PmhfX+Gwt5hQZLbkKZcgQ9XrhSKdMxVm1yy/fk0U457enlz5
 | 
						|
cKumubUxOfFdy1ZvAAAAEm5jY0BtYnAudWJudC5sb2NhbA==
 | 
						|
-----END OPENSSH PRIVATE KEY-----
 | 
						|
`
 | 
						|
	testKeyToSignPublic := `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCfVXbF38SAlchjnd8FO1zbST9qN6SpCeC+SkVLA0dbrdc0RArwuUU4LIZeiN70NQbGdizcX+iF+wEFaQs19Tt+IQ11IkvWluQCAn7a6FX2wtmO9iGKStdajIlcsMp9jXYuOlwFLYd4sxPTiVpn/FIms4EQkcbMfKvbBQ7Q1RBRA9Q4MAWGccqC+8DhS7v1RiBx1OEXjDzf4QCcTgnMFkwo8yPPLi7ULRH8ugPwO5qkfBfo0ZZ0RS9Rln5dFwhZ4I69HSd6oYFEffmTUy/uTrl3uSQTS68zlnfp6vDe12izPE9h/sAqc7UOl5Ta7AxzKbrE3B/DKcqOEuov8yCLi5H `
 | 
						|
 | 
						|
	// Old role entries between 1.4.3 and 1.5.2 had algorithm_signer default to
 | 
						|
	// ssh-rsa if not provided.
 | 
						|
	roleOptionsOldEntry := map[string]interface{}{
 | 
						|
		"allow_user_certificates": true,
 | 
						|
		"allowed_users":           "*",
 | 
						|
		"default_extensions": []map[string]string{
 | 
						|
			{
 | 
						|
				"permit-pty": "",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"key_type":         "ca",
 | 
						|
		"default_user":     testUserName,
 | 
						|
		"ttl":              "30m0s",
 | 
						|
		"algorithm_signer": ssh.SigAlgoRSA,
 | 
						|
	}
 | 
						|
 | 
						|
	// Upgrade entry by overwriting algorithm_signer with an empty value
 | 
						|
	roleOptionsUpgradedEntry := map[string]interface{}{
 | 
						|
		"allow_user_certificates": true,
 | 
						|
		"allowed_users":           "*",
 | 
						|
		"default_extensions": []map[string]string{
 | 
						|
			{
 | 
						|
				"permit-pty": "",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"key_type":         "ca",
 | 
						|
		"default_user":     testUserName,
 | 
						|
		"ttl":              "30m0s",
 | 
						|
		"algorithm_signer": "",
 | 
						|
	}
 | 
						|
 | 
						|
	testCase := logicaltest.TestCase{
 | 
						|
		LogicalBackend: b,
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			configCaStep(testCAPublicKey, testCAPrivateKey),
 | 
						|
			testRoleWrite(t, "testcarole", roleOptionsOldEntry),
 | 
						|
			testRoleWrite(t, "testcarole", roleOptionsUpgradedEntry),
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/testcarole",
 | 
						|
				ErrorOk:   false,
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key":       testKeyToSignPublic,
 | 
						|
					"valid_principals": testUserName,
 | 
						|
				},
 | 
						|
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					signedKey := strings.TrimSpace(resp.Data["signed_key"].(string))
 | 
						|
					if signedKey == "" {
 | 
						|
						return errors.New("no signed key in response")
 | 
						|
					}
 | 
						|
 | 
						|
					privKey, err := ssh.ParsePrivateKey([]byte(testKeyToSignPrivate))
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("error parsing private key: %v", err)
 | 
						|
					}
 | 
						|
 | 
						|
					parsedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(signedKey))
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("error parsing signed key: %v", err)
 | 
						|
					}
 | 
						|
					certSigner, err := ssh.NewCertSigner(parsedKey.(*ssh.Certificate), privKey)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
 | 
						|
					err = testSSH(testUserName, sshAddress, ssh.PublicKeys(certSigner), "date")
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, testCase)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_AbleToRetrievePublicKey(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),
 | 
						|
 | 
						|
			{
 | 
						|
				Operation:       logical.ReadOperation,
 | 
						|
				Path:            "public_key",
 | 
						|
				Unauthenticated: true,
 | 
						|
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					key := string(resp.Data["http_raw_body"].([]byte))
 | 
						|
 | 
						|
					if key != testCAPublicKey {
 | 
						|
						return fmt.Errorf("public_key incorrect. Expected %v, actual %v", testCAPublicKey, key)
 | 
						|
					}
 | 
						|
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, testCase)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_AbleToAutoGenerateSigningKeys(t *testing.T) {
 | 
						|
	config := logical.TestBackendConfig()
 | 
						|
 | 
						|
	b, err := Factory(context.Background(), config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Cannot create backend: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	var expectedPublicKey string
 | 
						|
	testCase := logicaltest.TestCase{
 | 
						|
		LogicalBackend: b,
 | 
						|
		Steps: []logicaltest.TestStep{
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "config/ca",
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					if resp.Data["public_key"].(string) == "" {
 | 
						|
						return fmt.Errorf("public_key empty")
 | 
						|
					}
 | 
						|
					expectedPublicKey = resp.Data["public_key"].(string)
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
 | 
						|
			{
 | 
						|
				Operation:       logical.ReadOperation,
 | 
						|
				Path:            "public_key",
 | 
						|
				Unauthenticated: true,
 | 
						|
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					key := string(resp.Data["http_raw_body"].([]byte))
 | 
						|
 | 
						|
					if key == "" {
 | 
						|
						return fmt.Errorf("public_key empty. Expected not empty, actual %s", key)
 | 
						|
					}
 | 
						|
					if key != expectedPublicKey {
 | 
						|
						return fmt.Errorf("public_key mismatch. Expected %s, actual %s", expectedPublicKey, key)
 | 
						|
					}
 | 
						|
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, testCase)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_ValidPrincipalsValidatedForHostCertificates(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",
 | 
						|
				},
 | 
						|
			}),
 | 
						|
 | 
						|
			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 TestBackend_OptionsOverrideDefaults(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",
 | 
						|
				"allowed_users":            "tuber",
 | 
						|
				"default_user":             "tuber",
 | 
						|
				"allow_user_certificates":  true,
 | 
						|
				"allowed_critical_options": "option,secondary",
 | 
						|
				"allowed_extensions":       "extension,additional",
 | 
						|
				"default_critical_options": map[string]interface{}{
 | 
						|
					"option": "value",
 | 
						|
				},
 | 
						|
				"default_extensions": map[string]interface{}{
 | 
						|
					"extension": "extended",
 | 
						|
				},
 | 
						|
			}),
 | 
						|
 | 
						|
			signCertificateStep("testing", "vault-root-22608f5ef173aabf700797cb95c5641e792698ec6380e8e1eb55523e39aa5e51", ssh.UserCert, []string{"tuber"}, map[string]string{
 | 
						|
				"secondary": "value",
 | 
						|
			}, map[string]string{
 | 
						|
				"additional": "value",
 | 
						|
			}, 2*time.Hour, map[string]interface{}{
 | 
						|
				"public_key": publicKey2,
 | 
						|
				"ttl":        "2h",
 | 
						|
				"critical_options": map[string]interface{}{
 | 
						|
					"secondary": "value",
 | 
						|
				},
 | 
						|
				"extensions": map[string]interface{}{
 | 
						|
					"additional": "value",
 | 
						|
				},
 | 
						|
			}),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, testCase)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_AllowedUserKeyLengths(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("weakkey", map[string]interface{}{
 | 
						|
				"key_type":                "ca",
 | 
						|
				"allow_user_certificates": true,
 | 
						|
				"allowed_user_key_lengths": map[string]interface{}{
 | 
						|
					"rsa": 4096,
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/weakkey",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": testCAPublicKey,
 | 
						|
				},
 | 
						|
				ErrorOk: true,
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					if resp.Data["error"] != "public_key failed to meet the key requirements: key is of an invalid size: 2048" {
 | 
						|
						return errors.New("a smaller key (2048) was allowed, when the minimum was set for 4096")
 | 
						|
					}
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
			createRoleStep("stdkey", map[string]interface{}{
 | 
						|
				"key_type":                "ca",
 | 
						|
				"allow_user_certificates": true,
 | 
						|
				"allowed_user_key_lengths": map[string]interface{}{
 | 
						|
					"rsa": 2048,
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			// Pass with 2048 key
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/stdkey",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": testCAPublicKey,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			// Fail with 4096 key
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/stdkey",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": publicKey4096,
 | 
						|
				},
 | 
						|
				ErrorOk: true,
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					if resp.Data["error"] != "public_key failed to meet the key requirements: key is of an invalid size: 4096" {
 | 
						|
						return errors.New("a larger key (4096) was allowed, when the size was set for 2048")
 | 
						|
					}
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
			createRoleStep("multikey", map[string]interface{}{
 | 
						|
				"key_type":                "ca",
 | 
						|
				"allow_user_certificates": true,
 | 
						|
				"allowed_user_key_lengths": map[string]interface{}{
 | 
						|
					"rsa": []int{2048, 4096},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			// Pass with 2048-bit key
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/multikey",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": testCAPublicKey,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			// Pass with 4096-bit key
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/multikey",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": publicKey4096,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			// Fail with 3072-bit key
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/multikey",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": publicKey3072,
 | 
						|
				},
 | 
						|
				ErrorOk: true,
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					if resp.Data["error"] != "public_key failed to meet the key requirements: key is of an invalid size: 3072" {
 | 
						|
						return errors.New("a larger key (3072) was allowed, when the size was set for 2048")
 | 
						|
					}
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
			// Fail with ECDSA key
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/multikey",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": publicKeyECDSA256,
 | 
						|
				},
 | 
						|
				ErrorOk: true,
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					if resp.Data["error"] != "public_key failed to meet the key requirements: key of type ecdsa is not allowed" {
 | 
						|
						return errors.New("an ECDSA key was allowed under RSA-only policy")
 | 
						|
					}
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
			createRoleStep("ectypes", map[string]interface{}{
 | 
						|
				"key_type":                "ca",
 | 
						|
				"allow_user_certificates": true,
 | 
						|
				"allowed_user_key_lengths": map[string]interface{}{
 | 
						|
					"ec":                  []int{256},
 | 
						|
					"ecdsa-sha2-nistp521": 0,
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			// Pass with ECDSA P-256
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/ectypes",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": publicKeyECDSA256,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			// Pass with ECDSA P-521
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/ectypes",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": publicKeyECDSA521,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			// Fail with RSA key
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/ectypes",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": publicKey3072,
 | 
						|
				},
 | 
						|
				ErrorOk: true,
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					if resp.Data["error"] != "public_key failed to meet the key requirements: key of type rsa is not allowed" {
 | 
						|
						return errors.New("an RSA key was allowed under ECDSA-only policy")
 | 
						|
					}
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, testCase)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_CustomKeyIDFormat(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("customrole", map[string]interface{}{
 | 
						|
				"key_type":                 "ca",
 | 
						|
				"key_id_format":            "{{role_name}}-{{token_display_name}}-{{public_key_hash}}",
 | 
						|
				"allowed_users":            "tuber",
 | 
						|
				"default_user":             "tuber",
 | 
						|
				"allow_user_certificates":  true,
 | 
						|
				"allowed_critical_options": "option,secondary",
 | 
						|
				"allowed_extensions":       "extension,additional",
 | 
						|
				"default_critical_options": map[string]interface{}{
 | 
						|
					"option": "value",
 | 
						|
				},
 | 
						|
				"default_extensions": map[string]interface{}{
 | 
						|
					"extension": "extended",
 | 
						|
				},
 | 
						|
			}),
 | 
						|
 | 
						|
			signCertificateStep("customrole", "customrole-root-22608f5ef173aabf700797cb95c5641e792698ec6380e8e1eb55523e39aa5e51", ssh.UserCert, []string{"tuber"}, map[string]string{
 | 
						|
				"secondary": "value",
 | 
						|
			}, map[string]string{
 | 
						|
				"additional": "value",
 | 
						|
			}, 2*time.Hour, map[string]interface{}{
 | 
						|
				"public_key": publicKey2,
 | 
						|
				"ttl":        "2h",
 | 
						|
				"critical_options": map[string]interface{}{
 | 
						|
					"secondary": "value",
 | 
						|
				},
 | 
						|
				"extensions": map[string]interface{}{
 | 
						|
					"additional": "value",
 | 
						|
				},
 | 
						|
			}),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, testCase)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_DisallowUserProvidedKeyIDs(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_user_key_ids":      false,
 | 
						|
				"allow_user_certificates": true,
 | 
						|
			}),
 | 
						|
			{
 | 
						|
				Operation: logical.UpdateOperation,
 | 
						|
				Path:      "sign/testing",
 | 
						|
				Data: map[string]interface{}{
 | 
						|
					"public_key": publicKey2,
 | 
						|
					"key_id":     "override",
 | 
						|
				},
 | 
						|
				ErrorOk: true,
 | 
						|
				Check: func(resp *logical.Response) error {
 | 
						|
					if resp.Data["error"] != "setting key_id is not allowed by role" {
 | 
						|
						return errors.New("custom user key id was allowed even when 'allow_user_key_ids' is false")
 | 
						|
					}
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, testCase)
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_DefExtTemplatingEnabled(t *testing.T) {
 | 
						|
	cluster, userpassToken := getSshCaTestCluster(t, testUserName)
 | 
						|
	defer cluster.Cleanup()
 | 
						|
	client := cluster.Cores[0].Client
 | 
						|
 | 
						|
	// Get auth accessor for identity template.
 | 
						|
	auths, err := client.Sys().ListAuth()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	userpassAccessor := auths["userpass/"].Accessor
 | 
						|
 | 
						|
	// Write SSH role.
 | 
						|
	_, err = client.Logical().Write("ssh/roles/test", map[string]interface{}{
 | 
						|
		"key_type":                    "ca",
 | 
						|
		"allowed_extensions":          "login@zipzap.com",
 | 
						|
		"allow_user_certificates":     true,
 | 
						|
		"allowed_users":               "tuber",
 | 
						|
		"default_user":                "tuber",
 | 
						|
		"default_extensions_template": true,
 | 
						|
		"default_extensions": map[string]interface{}{
 | 
						|
			"login@foobar.com": "{{identity.entity.aliases." + userpassAccessor + ".name}}",
 | 
						|
			"login@foobar2.com": "{{identity.entity.aliases." + userpassAccessor + ".name}}, " +
 | 
						|
				"{{identity.entity.aliases." + userpassAccessor + ".name}}_foobar",
 | 
						|
		},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	sshKeyID := "vault-userpass-" + testUserName + "-9bd0f01b7dfc50a13aa5e5cd11aea19276968755c8f1f9c98965d04147f30ed0"
 | 
						|
 | 
						|
	// Issue SSH certificate with default extensions templating enabled, and no user-provided extensions
 | 
						|
	client.SetToken(userpassToken)
 | 
						|
	resp, err := client.Logical().Write("ssh/sign/test", map[string]interface{}{
 | 
						|
		"public_key": publicKey4096,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	signedKey := resp.Data["signed_key"].(string)
 | 
						|
	key, _ := base64.StdEncoding.DecodeString(strings.Split(signedKey, " ")[1])
 | 
						|
 | 
						|
	parsedKey, err := ssh.ParsePublicKey(key)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	defaultExtensionPermissions := map[string]string{
 | 
						|
		"login@foobar.com":  testUserName,
 | 
						|
		"login@foobar2.com": fmt.Sprintf("%s, %s_foobar", testUserName, testUserName),
 | 
						|
	}
 | 
						|
 | 
						|
	err = validateSSHCertificate(parsedKey.(*ssh.Certificate), sshKeyID, ssh.UserCert, []string{"tuber"}, map[string]string{}, defaultExtensionPermissions, 16*time.Hour)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Issue SSH certificate with default extensions templating enabled, and user-provided extensions
 | 
						|
	// The certificate should only have the user-provided extensions, and no templated extensions
 | 
						|
	userProvidedExtensionPermissions := map[string]string{
 | 
						|
		"login@zipzap.com": "some_other_user_name",
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().Write("ssh/sign/test", map[string]interface{}{
 | 
						|
		"public_key": publicKey4096,
 | 
						|
		"extensions": userProvidedExtensionPermissions,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	signedKey = resp.Data["signed_key"].(string)
 | 
						|
	key, _ = base64.StdEncoding.DecodeString(strings.Split(signedKey, " ")[1])
 | 
						|
 | 
						|
	parsedKey, err = ssh.ParsePublicKey(key)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = validateSSHCertificate(parsedKey.(*ssh.Certificate), sshKeyID, ssh.UserCert, []string{"tuber"}, map[string]string{}, userProvidedExtensionPermissions, 16*time.Hour)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Issue SSH certificate with default extensions templating enabled, and invalid user-provided extensions - it should fail
 | 
						|
	invalidUserProvidedExtensionPermissions := map[string]string{
 | 
						|
		"login@foobar.com": "{{identity.entity.metadata}}",
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().Write("ssh/sign/test", map[string]interface{}{
 | 
						|
		"public_key": publicKey4096,
 | 
						|
		"extensions": invalidUserProvidedExtensionPermissions,
 | 
						|
	})
 | 
						|
	if err == nil {
 | 
						|
		t.Fatal("expected an error while attempting to sign a key with invalid permissions")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_EmptyAllowedExtensionFailsClosed(t *testing.T) {
 | 
						|
	cluster, userpassToken := getSshCaTestCluster(t, testUserName)
 | 
						|
	defer cluster.Cleanup()
 | 
						|
	client := cluster.Cores[0].Client
 | 
						|
 | 
						|
	// Get auth accessor for identity template.
 | 
						|
	auths, err := client.Sys().ListAuth()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	userpassAccessor := auths["userpass/"].Accessor
 | 
						|
 | 
						|
	// Write SSH role to test with no allowed extension. We also provide a templated default extension,
 | 
						|
	// to verify that it's not actually being evaluated
 | 
						|
	_, err = client.Logical().Write("ssh/roles/test_allow_all_extensions", map[string]interface{}{
 | 
						|
		"key_type":                    "ca",
 | 
						|
		"allow_user_certificates":     true,
 | 
						|
		"allowed_users":               "tuber",
 | 
						|
		"default_user":                "tuber",
 | 
						|
		"allowed_extensions":          "",
 | 
						|
		"default_extensions_template": false,
 | 
						|
		"default_extensions": map[string]interface{}{
 | 
						|
			"login@foobar.com": "{{identity.entity.aliases." + userpassAccessor + ".name}}",
 | 
						|
		},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Issue SSH certificate with default extensions templating disabled, and user-provided extensions
 | 
						|
	client.SetToken(userpassToken)
 | 
						|
	userProvidedAnyExtensionPermissions := map[string]string{
 | 
						|
		"login@foobar.com": "not_userpassname",
 | 
						|
	}
 | 
						|
	_, err = client.Logical().Write("ssh/sign/test_allow_all_extensions", map[string]interface{}{
 | 
						|
		"public_key": publicKey4096,
 | 
						|
		"extensions": userProvidedAnyExtensionPermissions,
 | 
						|
	})
 | 
						|
	if err == nil {
 | 
						|
		t.Fatal("Expected failure we should not have allowed specifying custom extensions")
 | 
						|
	}
 | 
						|
 | 
						|
	if !strings.Contains(err.Error(), "are not on allowed list") {
 | 
						|
		t.Fatalf("Expected failure to contain 'are not on allowed list' but was %s", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_DefExtTemplatingDisabled(t *testing.T) {
 | 
						|
	cluster, userpassToken := getSshCaTestCluster(t, testUserName)
 | 
						|
	defer cluster.Cleanup()
 | 
						|
	client := cluster.Cores[0].Client
 | 
						|
 | 
						|
	// Get auth accessor for identity template.
 | 
						|
	auths, err := client.Sys().ListAuth()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	userpassAccessor := auths["userpass/"].Accessor
 | 
						|
 | 
						|
	// Write SSH role to test with any extension. We also provide a templated default extension,
 | 
						|
	// to verify that it's not actually being evaluated
 | 
						|
	_, err = client.Logical().Write("ssh/roles/test_allow_all_extensions", map[string]interface{}{
 | 
						|
		"key_type":                    "ca",
 | 
						|
		"allow_user_certificates":     true,
 | 
						|
		"allowed_users":               "tuber",
 | 
						|
		"default_user":                "tuber",
 | 
						|
		"allowed_extensions":          "*",
 | 
						|
		"default_extensions_template": false,
 | 
						|
		"default_extensions": map[string]interface{}{
 | 
						|
			"login@foobar.com": "{{identity.entity.aliases." + userpassAccessor + ".name}}",
 | 
						|
		},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	sshKeyID := "vault-userpass-" + testUserName + "-9bd0f01b7dfc50a13aa5e5cd11aea19276968755c8f1f9c98965d04147f30ed0"
 | 
						|
 | 
						|
	// Issue SSH certificate with default extensions templating disabled, and no user-provided extensions
 | 
						|
	client.SetToken(userpassToken)
 | 
						|
	defaultExtensionPermissions := map[string]string{
 | 
						|
		"login@foobar.com": "{{identity.entity.aliases." + userpassAccessor + ".name}}",
 | 
						|
		"login@zipzap.com": "some_other_user_name",
 | 
						|
	}
 | 
						|
	resp, err := client.Logical().Write("ssh/sign/test_allow_all_extensions", map[string]interface{}{
 | 
						|
		"public_key": publicKey4096,
 | 
						|
		"extensions": defaultExtensionPermissions,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	signedKey := resp.Data["signed_key"].(string)
 | 
						|
	key, _ := base64.StdEncoding.DecodeString(strings.Split(signedKey, " ")[1])
 | 
						|
 | 
						|
	parsedKey, err := ssh.ParsePublicKey(key)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = validateSSHCertificate(parsedKey.(*ssh.Certificate), sshKeyID, ssh.UserCert, []string{"tuber"}, map[string]string{}, defaultExtensionPermissions, 16*time.Hour)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Issue SSH certificate with default extensions templating disabled, and user-provided extensions
 | 
						|
	client.SetToken(userpassToken)
 | 
						|
	userProvidedAnyExtensionPermissions := map[string]string{
 | 
						|
		"login@foobar.com": "not_userpassname",
 | 
						|
		"login@zipzap.com": "some_other_user_name",
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().Write("ssh/sign/test_allow_all_extensions", map[string]interface{}{
 | 
						|
		"public_key": publicKey4096,
 | 
						|
		"extensions": userProvidedAnyExtensionPermissions,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	signedKey = resp.Data["signed_key"].(string)
 | 
						|
	key, _ = base64.StdEncoding.DecodeString(strings.Split(signedKey, " ")[1])
 | 
						|
 | 
						|
	parsedKey, err = ssh.ParsePublicKey(key)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = validateSSHCertificate(parsedKey.(*ssh.Certificate), sshKeyID, ssh.UserCert, []string{"tuber"}, map[string]string{}, userProvidedAnyExtensionPermissions, 16*time.Hour)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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 TestSSHBackend_IssueSign(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":     "otp",
 | 
						|
				"default_user": "user",
 | 
						|
			}),
 | 
						|
			// Key pair not issued with invalid role key type
 | 
						|
			issueSSHKeyPairStep("testing", "rsa", 0, true, "role key type 'otp' not allowed to issue key pairs"),
 | 
						|
 | 
						|
			createRoleStep("testing", map[string]interface{}{
 | 
						|
				"key_type":                "ca",
 | 
						|
				"allow_user_key_ids":      false,
 | 
						|
				"allow_user_certificates": true,
 | 
						|
				"allowed_user_key_lengths": map[string]interface{}{
 | 
						|
					"ssh-rsa":             []int{2048, 3072, 4096},
 | 
						|
					"ecdsa-sha2-nistp521": 0,
 | 
						|
					"ed25519":             0,
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			// Key_type not in allowed_user_key_types_lengths
 | 
						|
			issueSSHKeyPairStep("testing", "ec", 256, true, "provided key_type value not in allowed_user_key_types"),
 | 
						|
			// Key_bits not in allowed_user_key_types_lengths for provided key_type
 | 
						|
			issueSSHKeyPairStep("testing", "rsa", 2560, true, "provided key_bits value not in list of role's allowed_user_key_types"),
 | 
						|
			// key_type `rsa` and key_bits `2048` successfully created
 | 
						|
			issueSSHKeyPairStep("testing", "rsa", 2048, false, ""),
 | 
						|
			// key_type `ed22519` and key_bits `0` successfully created
 | 
						|
			issueSSHKeyPairStep("testing", "ed25519", 0, false, ""),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	logicaltest.Test(t, testCase)
 | 
						|
}
 | 
						|
 | 
						|
func getSshCaTestCluster(t *testing.T, userIdentity string) (*vault.TestCluster, string) {
 | 
						|
	coreConfig := &vault.CoreConfig{
 | 
						|
		CredentialBackends: map[string]logical.Factory{
 | 
						|
			"userpass": userpass.Factory,
 | 
						|
		},
 | 
						|
		LogicalBackends: map[string]logical.Factory{
 | 
						|
			"ssh": Factory,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
 | 
						|
		HandlerFunc: vaulthttp.Handler,
 | 
						|
	})
 | 
						|
	cluster.Start()
 | 
						|
	client := cluster.Cores[0].Client
 | 
						|
 | 
						|
	// Write test policy for userpass auth method.
 | 
						|
	err := client.Sys().PutPolicy("test", `
 | 
						|
   path "ssh/*" {
 | 
						|
     capabilities = ["update"]
 | 
						|
   }`)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Enable userpass auth method.
 | 
						|
	if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Configure test role for userpass.
 | 
						|
	if _, err := client.Logical().Write("auth/userpass/users/"+userIdentity, map[string]interface{}{
 | 
						|
		"password": "test",
 | 
						|
		"policies": "test",
 | 
						|
	}); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Login userpass for test role and keep client token.
 | 
						|
	secret, err := client.Logical().Write("auth/userpass/login/"+userIdentity, map[string]interface{}{
 | 
						|
		"password": "test",
 | 
						|
	})
 | 
						|
	if err != nil || secret == nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	userpassToken := secret.Auth.ClientToken
 | 
						|
 | 
						|
	// Mount SSH.
 | 
						|
	err = client.Sys().Mount("ssh", &api.MountInput{
 | 
						|
		Type: "ssh",
 | 
						|
		Config: api.MountConfigInput{
 | 
						|
			DefaultLeaseTTL: "16h",
 | 
						|
			MaxLeaseTTL:     "60h",
 | 
						|
		},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Configure SSH CA.
 | 
						|
	_, err = client.Logical().Write("ssh/config/ca", map[string]interface{}{
 | 
						|
		"public_key":  testCAPublicKey,
 | 
						|
		"private_key": testCAPrivateKey,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return cluster, userpassToken
 | 
						|
}
 | 
						|
 | 
						|
func testDefaultUserTemplate(t *testing.T, testDefaultUserTemplate string,
 | 
						|
	expectedValidPrincipal string, testEntityMetadata map[string]string,
 | 
						|
) {
 | 
						|
	cluster, userpassToken := getSshCaTestCluster(t, testUserName)
 | 
						|
	defer cluster.Cleanup()
 | 
						|
	client := cluster.Cores[0].Client
 | 
						|
 | 
						|
	// set metadata "ssh_username" to userpass username
 | 
						|
	tokenLookupResponse, err := client.Logical().Write("/auth/token/lookup", map[string]interface{}{
 | 
						|
		"token": userpassToken,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	entityID := tokenLookupResponse.Data["entity_id"].(string)
 | 
						|
	_, err = client.Logical().Write("/identity/entity/id/"+entityID, map[string]interface{}{
 | 
						|
		"metadata": testEntityMetadata,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = client.Logical().Write("ssh/roles/my-role", map[string]interface{}{
 | 
						|
		"key_type":                testCaKeyType,
 | 
						|
		"allow_user_certificates": true,
 | 
						|
		"default_user":            testDefaultUserTemplate,
 | 
						|
		"default_user_template":   true,
 | 
						|
		"allowed_users":           testDefaultUserTemplate,
 | 
						|
		"allowed_users_template":  true,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// sign SSH key as userpass user
 | 
						|
	client.SetToken(userpassToken)
 | 
						|
	signResponse, err := client.Logical().Write("ssh/sign/my-role", map[string]interface{}{
 | 
						|
		"public_key": testCAPublicKey,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// check for the expected valid principals of certificate
 | 
						|
	signedKey := signResponse.Data["signed_key"].(string)
 | 
						|
	key, _ := base64.StdEncoding.DecodeString(strings.Split(signedKey, " ")[1])
 | 
						|
	parsedKey, err := ssh.ParsePublicKey(key)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	actualPrincipals := parsedKey.(*ssh.Certificate).ValidPrincipals
 | 
						|
	if actualPrincipals[0] != expectedValidPrincipal {
 | 
						|
		t.Fatal(
 | 
						|
			fmt.Sprintf("incorrect ValidPrincipals: %v should be %v",
 | 
						|
				actualPrincipals, []string{expectedValidPrincipal}),
 | 
						|
		)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testAllowedPrincipalsTemplate(t *testing.T, testAllowedDomainsTemplate string,
 | 
						|
	expectedValidPrincipal string, testEntityMetadata map[string]string,
 | 
						|
	roleConfigPayload map[string]interface{}, signingPayload map[string]interface{},
 | 
						|
) {
 | 
						|
	cluster, userpassToken := getSshCaTestCluster(t, testUserName)
 | 
						|
	defer cluster.Cleanup()
 | 
						|
	client := cluster.Cores[0].Client
 | 
						|
 | 
						|
	// set metadata "ssh_username" to userpass username
 | 
						|
	tokenLookupResponse, err := client.Logical().Write("/auth/token/lookup", map[string]interface{}{
 | 
						|
		"token": userpassToken,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	entityID := tokenLookupResponse.Data["entity_id"].(string)
 | 
						|
	_, err = client.Logical().Write("/identity/entity/id/"+entityID, map[string]interface{}{
 | 
						|
		"metadata": testEntityMetadata,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = client.Logical().Write("ssh/roles/my-role", roleConfigPayload)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// sign SSH key as userpass user
 | 
						|
	client.SetToken(userpassToken)
 | 
						|
	signResponse, err := client.Logical().Write("ssh/sign/my-role", signingPayload)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// check for the expected valid principals of certificate
 | 
						|
	signedKey := signResponse.Data["signed_key"].(string)
 | 
						|
	key, _ := base64.StdEncoding.DecodeString(strings.Split(signedKey, " ")[1])
 | 
						|
	parsedKey, err := ssh.ParsePublicKey(key)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	actualPrincipals := parsedKey.(*ssh.Certificate).ValidPrincipals
 | 
						|
	if actualPrincipals[0] != expectedValidPrincipal {
 | 
						|
		t.Fatal(
 | 
						|
			fmt.Sprintf("incorrect ValidPrincipals: %v should be %v",
 | 
						|
				actualPrincipals, []string{expectedValidPrincipal}),
 | 
						|
		)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testAllowedUsersTemplate(t *testing.T, testAllowedUsersTemplate string,
 | 
						|
	expectedValidPrincipal string, testEntityMetadata map[string]string,
 | 
						|
) {
 | 
						|
	testAllowedPrincipalsTemplate(
 | 
						|
		t, testAllowedUsersTemplate,
 | 
						|
		expectedValidPrincipal, testEntityMetadata,
 | 
						|
		map[string]interface{}{
 | 
						|
			"key_type":                testCaKeyType,
 | 
						|
			"allow_user_certificates": true,
 | 
						|
			"allowed_users":           testAllowedUsersTemplate,
 | 
						|
			"allowed_users_template":  true,
 | 
						|
		},
 | 
						|
		map[string]interface{}{
 | 
						|
			"public_key":       testCAPublicKey,
 | 
						|
			"valid_principals": expectedValidPrincipal,
 | 
						|
		},
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
func configCaStep(caPublicKey, caPrivateKey string) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Path:      "config/ca",
 | 
						|
		Data: map[string]interface{}{
 | 
						|
			"public_key":  caPublicKey,
 | 
						|
			"private_key": caPrivateKey,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func createRoleStep(name string, parameters map[string]interface{}) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.CreateOperation,
 | 
						|
		Path:      "roles/" + name,
 | 
						|
		Data:      parameters,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func signCertificateStep(
 | 
						|
	role, keyID string, certType int, validPrincipals []string,
 | 
						|
	criticalOptionPermissions, extensionPermissions map[string]string,
 | 
						|
	ttl time.Duration,
 | 
						|
	requestParameters map[string]interface{},
 | 
						|
) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Path:      "sign/" + role,
 | 
						|
		Data:      requestParameters,
 | 
						|
 | 
						|
		Check: func(resp *logical.Response) error {
 | 
						|
			serialNumber := resp.Data["serial_number"].(string)
 | 
						|
			if serialNumber == "" {
 | 
						|
				return errors.New("no serial number in response")
 | 
						|
			}
 | 
						|
 | 
						|
			signedKey := strings.TrimSpace(resp.Data["signed_key"].(string))
 | 
						|
			if signedKey == "" {
 | 
						|
				return errors.New("no signed key in response")
 | 
						|
			}
 | 
						|
 | 
						|
			key, _ := base64.StdEncoding.DecodeString(strings.Split(signedKey, " ")[1])
 | 
						|
 | 
						|
			parsedKey, err := ssh.ParsePublicKey(key)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			return validateSSHCertificate(parsedKey.(*ssh.Certificate), keyID, certType, validPrincipals, criticalOptionPermissions, extensionPermissions, ttl)
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func issueSSHKeyPairStep(role, keyType string, keyBits int, expectError bool, errorMsg string) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Path:      "issue/" + role,
 | 
						|
		Data: map[string]interface{}{
 | 
						|
			"key_type": keyType,
 | 
						|
			"key_bits": keyBits,
 | 
						|
		},
 | 
						|
		ErrorOk: true,
 | 
						|
		Check: func(resp *logical.Response) error {
 | 
						|
			if expectError {
 | 
						|
				var err error
 | 
						|
				if resp.Data["error"] != errorMsg {
 | 
						|
					err = fmt.Errorf("actual error message \"%s\" different from expected error message \"%s\"", resp.Data["error"], errorMsg)
 | 
						|
				}
 | 
						|
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			if resp.IsError() {
 | 
						|
				return fmt.Errorf("unexpected error response returned: %v", resp.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if resp.Data["private_key_type"] != keyType {
 | 
						|
				return fmt.Errorf("response private_key_type (%s) does not match the provided key_type (%s)", resp.Data["private_key_type"], keyType)
 | 
						|
			}
 | 
						|
 | 
						|
			if resp.Data["signed_key"] == "" {
 | 
						|
				return errors.New("certificate/signed_key should not be empty")
 | 
						|
			}
 | 
						|
 | 
						|
			return nil
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func validateSSHCertificate(cert *ssh.Certificate, keyID string, certType int, validPrincipals []string, criticalOptionPermissions, extensionPermissions map[string]string,
 | 
						|
	ttl time.Duration,
 | 
						|
) error {
 | 
						|
	if cert.KeyId != keyID {
 | 
						|
		return fmt.Errorf("incorrect KeyId: %v, wanted %v", cert.KeyId, keyID)
 | 
						|
	}
 | 
						|
 | 
						|
	if cert.CertType != uint32(certType) {
 | 
						|
		return fmt.Errorf("incorrect CertType: %v", cert.CertType)
 | 
						|
	}
 | 
						|
 | 
						|
	if time.Unix(int64(cert.ValidAfter), 0).After(time.Now()) {
 | 
						|
		return fmt.Errorf("incorrect ValidAfter: %v", cert.ValidAfter)
 | 
						|
	}
 | 
						|
 | 
						|
	if time.Unix(int64(cert.ValidBefore), 0).Before(time.Now()) {
 | 
						|
		return fmt.Errorf("incorrect ValidBefore: %v", cert.ValidBefore)
 | 
						|
	}
 | 
						|
 | 
						|
	actualTTL := time.Unix(int64(cert.ValidBefore), 0).Add(-30 * time.Second).Sub(time.Unix(int64(cert.ValidAfter), 0))
 | 
						|
	if actualTTL != ttl {
 | 
						|
		return fmt.Errorf("incorrect ttl: expected: %v, actual %v", ttl, actualTTL)
 | 
						|
	}
 | 
						|
 | 
						|
	if !reflect.DeepEqual(cert.ValidPrincipals, validPrincipals) {
 | 
						|
		return fmt.Errorf("incorrect ValidPrincipals: expected: %#v actual: %#v", validPrincipals, cert.ValidPrincipals)
 | 
						|
	}
 | 
						|
 | 
						|
	publicSigningKey, err := getSigningPublicKey()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if !reflect.DeepEqual(cert.SignatureKey, publicSigningKey) {
 | 
						|
		return fmt.Errorf("incorrect SignatureKey: %v", cert.SignatureKey)
 | 
						|
	}
 | 
						|
 | 
						|
	if cert.Signature == nil {
 | 
						|
		return fmt.Errorf("incorrect Signature: %v", cert.Signature)
 | 
						|
	}
 | 
						|
 | 
						|
	if !reflect.DeepEqual(cert.Permissions.Extensions, extensionPermissions) {
 | 
						|
		return fmt.Errorf("incorrect Permissions.Extensions: Expected: %v, Actual: %v", extensionPermissions, cert.Permissions.Extensions)
 | 
						|
	}
 | 
						|
 | 
						|
	if !reflect.DeepEqual(cert.Permissions.CriticalOptions, criticalOptionPermissions) {
 | 
						|
		return fmt.Errorf("incorrect Permissions.CriticalOptions: %v", cert.Permissions.CriticalOptions)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func getSigningPublicKey() (ssh.PublicKey, error) {
 | 
						|
	key, err := base64.StdEncoding.DecodeString(strings.Split(testCAPublicKey, " ")[1])
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	parsedKey, err := ssh.ParsePublicKey(key)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return parsedKey, nil
 | 
						|
}
 | 
						|
 | 
						|
func testConfigZeroAddressDelete(t *testing.T) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.DeleteOperation,
 | 
						|
		Path:      "config/zeroaddress",
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testConfigZeroAddressWrite(t *testing.T, data map[string]interface{}) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Path:      "config/zeroaddress",
 | 
						|
		Data:      data,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testConfigZeroAddressRead(t *testing.T, expected map[string]interface{}) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.ReadOperation,
 | 
						|
		Path:      "config/zeroaddress",
 | 
						|
		Check: func(resp *logical.Response) error {
 | 
						|
			var d zeroAddressRoles
 | 
						|
			if err := mapstructure.Decode(resp.Data, &d); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			var ex zeroAddressRoles
 | 
						|
			if err := mapstructure.Decode(expected, &ex); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			if !reflect.DeepEqual(d, ex) {
 | 
						|
				return fmt.Errorf("Response mismatch:\nActual:%#v\nExpected:%#v", d, ex)
 | 
						|
			}
 | 
						|
 | 
						|
			return nil
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testVerifyWrite(t *testing.T, data map[string]interface{}, expected map[string]interface{}) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Path:      fmt.Sprintf("verify"),
 | 
						|
		Data:      data,
 | 
						|
		Check: func(resp *logical.Response) error {
 | 
						|
			var ac api.SSHVerifyResponse
 | 
						|
			if err := mapstructure.Decode(resp.Data, &ac); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			var ex api.SSHVerifyResponse
 | 
						|
			if err := mapstructure.Decode(expected, &ex); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			if !reflect.DeepEqual(ac, ex) {
 | 
						|
				return fmt.Errorf("invalid response")
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testLookupRead(t *testing.T, data map[string]interface{}, expected []string) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Path:      "lookup",
 | 
						|
		Data:      data,
 | 
						|
		Check: func(resp *logical.Response) error {
 | 
						|
			if resp.Data == nil || resp.Data["roles"] == nil {
 | 
						|
				return fmt.Errorf("missing roles information")
 | 
						|
			}
 | 
						|
			if !reflect.DeepEqual(resp.Data["roles"].([]string), expected) {
 | 
						|
				return fmt.Errorf("Invalid response: \nactual:%#v\nexpected:%#v", resp.Data["roles"].([]string), expected)
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testRoleWrite(t *testing.T, name string, data map[string]interface{}) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Path:      "roles/" + name,
 | 
						|
		Data:      data,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testRoleList(t *testing.T, expected map[string]interface{}) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.ListOperation,
 | 
						|
		Path:      "roles",
 | 
						|
		Check: func(resp *logical.Response) error {
 | 
						|
			if resp == nil {
 | 
						|
				return fmt.Errorf("nil response")
 | 
						|
			}
 | 
						|
			if resp.Data == nil {
 | 
						|
				return fmt.Errorf("nil data")
 | 
						|
			}
 | 
						|
			if !reflect.DeepEqual(resp.Data, expected) {
 | 
						|
				return fmt.Errorf("Invalid response:\nactual:%#v\nexpected is %#v", resp.Data, expected)
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testRoleRead(t *testing.T, roleName string, expected map[string]interface{}) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.ReadOperation,
 | 
						|
		Path:      "roles/" + roleName,
 | 
						|
		Check: func(resp *logical.Response) error {
 | 
						|
			if resp == nil {
 | 
						|
				if expected == nil {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
				return fmt.Errorf("bad: %#v", resp)
 | 
						|
			}
 | 
						|
			var d sshRole
 | 
						|
			if err := mapstructure.Decode(resp.Data, &d); err != nil {
 | 
						|
				return fmt.Errorf("error decoding response:%s", err)
 | 
						|
			}
 | 
						|
			switch d.KeyType {
 | 
						|
			case "otp":
 | 
						|
				if d.KeyType != expected["key_type"] || d.DefaultUser != expected["default_user"] || d.CIDRList != expected["cidr_list"] {
 | 
						|
					return fmt.Errorf("data mismatch. bad: %#v", resp)
 | 
						|
				}
 | 
						|
			default:
 | 
						|
				return fmt.Errorf("unknown key type. bad: %#v", resp)
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testRoleDelete(t *testing.T, name string) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.DeleteOperation,
 | 
						|
		Path:      "roles/" + name,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testCredsWrite(t *testing.T, roleName string, data map[string]interface{}, expectError bool, address string) logicaltest.TestStep {
 | 
						|
	return logicaltest.TestStep{
 | 
						|
		Operation: logical.UpdateOperation,
 | 
						|
		Path:      fmt.Sprintf("creds/%s", roleName),
 | 
						|
		Data:      data,
 | 
						|
		ErrorOk:   expectError,
 | 
						|
		Check: func(resp *logical.Response) error {
 | 
						|
			if resp == nil {
 | 
						|
				return fmt.Errorf("response is nil")
 | 
						|
			}
 | 
						|
			if resp.Data == nil {
 | 
						|
				return fmt.Errorf("data is nil")
 | 
						|
			}
 | 
						|
			if expectError {
 | 
						|
				var e struct {
 | 
						|
					Error string `mapstructure:"error"`
 | 
						|
				}
 | 
						|
				if err := mapstructure.Decode(resp.Data, &e); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
				if len(e.Error) == 0 {
 | 
						|
					return fmt.Errorf("expected error, but write succeeded")
 | 
						|
				}
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
			if roleName == testAtRoleName {
 | 
						|
				var d struct {
 | 
						|
					Key string `mapstructure:"key"`
 | 
						|
				}
 | 
						|
				if err := mapstructure.Decode(resp.Data, &d); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
				if d.Key == "" {
 | 
						|
					return fmt.Errorf("generated key is an empty string")
 | 
						|
				}
 | 
						|
				// Checking only for a parsable key
 | 
						|
				privKey, err := ssh.ParsePrivateKey([]byte(d.Key))
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("generated key is invalid")
 | 
						|
				}
 | 
						|
				if err := testSSH(data["username"].(string), address, ssh.PublicKeys(privKey), "date"); err != nil {
 | 
						|
					return fmt.Errorf("unable to SSH with new key (%s): %w", d.Key, err)
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				if resp.Data["key_type"] != KeyTypeOTP {
 | 
						|
					return fmt.Errorf("incorrect key_type")
 | 
						|
				}
 | 
						|
				if resp.Data["key"] == nil {
 | 
						|
					return fmt.Errorf("invalid key")
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestBackend_CleanupDynamicHostKeys(t *testing.T) {
 | 
						|
	config := logical.TestBackendConfig()
 | 
						|
	config.StorageView = &logical.InmemStorage{}
 | 
						|
 | 
						|
	b, err := Backend(config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	err = b.Setup(context.Background(), config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Running on a clean mount shouldn't do anything.
 | 
						|
	cleanRequest := &logical.Request{
 | 
						|
		Operation: logical.DeleteOperation,
 | 
						|
		Path:      "tidy/dynamic-keys",
 | 
						|
		Storage:   config.StorageView,
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err := b.HandleRequest(context.Background(), cleanRequest)
 | 
						|
	require.NoError(t, err)
 | 
						|
	require.NotNil(t, resp)
 | 
						|
	require.NotNil(t, resp.Data)
 | 
						|
	require.NotNil(t, resp.Data["message"])
 | 
						|
	require.Contains(t, resp.Data["message"], "0 of 0")
 | 
						|
 | 
						|
	// Write a bunch of bogus entries.
 | 
						|
	for i := 0; i < 15; i++ {
 | 
						|
		data := map[string]interface{}{
 | 
						|
			"host": "localhost",
 | 
						|
			"key":  "nothing-to-see-here",
 | 
						|
		}
 | 
						|
		entry, err := logical.StorageEntryJSON(fmt.Sprintf("%vexample-%v", keysStoragePrefix, i), &data)
 | 
						|
		require.NoError(t, err)
 | 
						|
		err = config.StorageView.Put(context.Background(), entry)
 | 
						|
		require.NoError(t, err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Should now have 15
 | 
						|
	resp, err = b.HandleRequest(context.Background(), cleanRequest)
 | 
						|
	require.NoError(t, err)
 | 
						|
	require.NotNil(t, resp)
 | 
						|
	require.NotNil(t, resp.Data)
 | 
						|
	require.NotNil(t, resp.Data["message"])
 | 
						|
	require.Contains(t, resp.Data["message"], "15 of 15")
 | 
						|
 | 
						|
	// Should have none left.
 | 
						|
	resp, err = b.HandleRequest(context.Background(), cleanRequest)
 | 
						|
	require.NoError(t, err)
 | 
						|
	require.NotNil(t, resp)
 | 
						|
	require.NotNil(t, resp.Data)
 | 
						|
	require.NotNil(t, resp.Data["message"])
 | 
						|
	require.Contains(t, resp.Data["message"], "0 of 0")
 | 
						|
}
 | 
						|
 | 
						|
type pathAuthCheckerFunc func(t *testing.T, client *api.Client, path string, token string)
 | 
						|
 | 
						|
func isPermDenied(err error) bool {
 | 
						|
	return strings.Contains(err.Error(), "permission denied")
 | 
						|
}
 | 
						|
 | 
						|
func isUnsupportedPathOperation(err error) bool {
 | 
						|
	return strings.Contains(err.Error(), "unsupported path") || strings.Contains(err.Error(), "unsupported operation")
 | 
						|
}
 | 
						|
 | 
						|
func isDeniedOp(err error) bool {
 | 
						|
	return isPermDenied(err) || isUnsupportedPathOperation(err)
 | 
						|
}
 | 
						|
 | 
						|
func pathShouldBeAuthed(t *testing.T, client *api.Client, path string, token string) {
 | 
						|
	client.SetToken("")
 | 
						|
	resp, err := client.Logical().ReadWithContext(ctx, path)
 | 
						|
	if err == nil || !isPermDenied(err) {
 | 
						|
		t.Fatalf("expected failure to read %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().ListWithContext(ctx, path)
 | 
						|
	if err == nil || !isPermDenied(err) {
 | 
						|
		t.Fatalf("expected failure to list %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().WriteWithContext(ctx, path, map[string]interface{}{})
 | 
						|
	if err == nil || !isPermDenied(err) {
 | 
						|
		t.Fatalf("expected failure to write %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().DeleteWithContext(ctx, path)
 | 
						|
	if err == nil || !isPermDenied(err) {
 | 
						|
		t.Fatalf("expected failure to delete %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{})
 | 
						|
	if err == nil || !isPermDenied(err) {
 | 
						|
		t.Fatalf("expected failure to patch %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func pathShouldBeUnauthedReadList(t *testing.T, client *api.Client, path string, token string) {
 | 
						|
	// Should be able to read both with and without a token.
 | 
						|
	client.SetToken("")
 | 
						|
	resp, err := client.Logical().ReadWithContext(ctx, path)
 | 
						|
	if err != nil && isPermDenied(err) {
 | 
						|
		// Read will sometimes return permission denied, when the handler
 | 
						|
		// does not support the given operation. Retry with the token.
 | 
						|
		client.SetToken(token)
 | 
						|
		resp2, err2 := client.Logical().ReadWithContext(ctx, path)
 | 
						|
		if err2 != nil && !isUnsupportedPathOperation(err2) {
 | 
						|
			t.Fatalf("unexpected failure to read %v while unauthed: %v / %v\nWhile authed: %v / %v", path, err, resp, err2, resp2)
 | 
						|
		}
 | 
						|
		client.SetToken("")
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().ListWithContext(ctx, path)
 | 
						|
	if err != nil && isPermDenied(err) {
 | 
						|
		// List will sometimes return permission denied, when the handler
 | 
						|
		// does not support the given operation. Retry with the token.
 | 
						|
		client.SetToken(token)
 | 
						|
		resp2, err2 := client.Logical().ListWithContext(ctx, path)
 | 
						|
		if err2 != nil && !isUnsupportedPathOperation(err2) {
 | 
						|
			t.Fatalf("unexpected failure to list %v while unauthed: %v / %v\nWhile authed: %v / %v", path, err, resp, err2, resp2)
 | 
						|
		}
 | 
						|
		client.SetToken("")
 | 
						|
	}
 | 
						|
 | 
						|
	// These should all be denied.
 | 
						|
	resp, err = client.Logical().WriteWithContext(ctx, path, map[string]interface{}{})
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during write on read-only path %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().DeleteWithContext(ctx, path)
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during delete on read-only path %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{})
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during patch on read-only path %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
 | 
						|
	// Retrying with token should allow read/list, but not modification still.
 | 
						|
	client.SetToken(token)
 | 
						|
	resp, err = client.Logical().ReadWithContext(ctx, path)
 | 
						|
	if err != nil && isPermDenied(err) {
 | 
						|
		t.Fatalf("unexpected failure to read %v while authed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().ListWithContext(ctx, path)
 | 
						|
	if err != nil && isPermDenied(err) {
 | 
						|
		t.Fatalf("unexpected failure to list %v while authed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
 | 
						|
	// Should all be denied.
 | 
						|
	resp, err = client.Logical().WriteWithContext(ctx, path, map[string]interface{}{})
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during write on read-only path %v while authed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().DeleteWithContext(ctx, path)
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during delete on read-only path %v while authed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{})
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during patch on read-only path %v while authed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func pathShouldBeUnauthedWriteOnly(t *testing.T, client *api.Client, path string, token string) {
 | 
						|
	client.SetToken("")
 | 
						|
	resp, err := client.Logical().WriteWithContext(ctx, path, map[string]interface{}{})
 | 
						|
	if err != nil && isPermDenied(err) {
 | 
						|
		t.Fatalf("unexpected failure to write %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
 | 
						|
	// These should all be denied.
 | 
						|
	resp, err = client.Logical().ReadWithContext(ctx, path)
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during read on write-only path %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().ListWithContext(ctx, path)
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during list on write-only path %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().DeleteWithContext(ctx, path)
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during delete on write-only path %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{})
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during patch on write-only path %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
 | 
						|
	// Retrying with token should allow writing, but nothing else.
 | 
						|
	client.SetToken(token)
 | 
						|
	resp, err = client.Logical().WriteWithContext(ctx, path, map[string]interface{}{})
 | 
						|
	if err != nil && isPermDenied(err) {
 | 
						|
		t.Fatalf("unexpected failure to write %v while unauthed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
 | 
						|
	// These should all be denied.
 | 
						|
	resp, err = client.Logical().ReadWithContext(ctx, path)
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during read on write-only path %v while authed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().ListWithContext(ctx, path)
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		if resp != nil || err != nil {
 | 
						|
			t.Fatalf("unexpected failure during list on write-only path %v while authed: %v / %v", path, err, resp)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().DeleteWithContext(ctx, path)
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during delete on write-only path %v while authed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
	resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{})
 | 
						|
	if err == nil || !isDeniedOp(err) {
 | 
						|
		t.Fatalf("unexpected failure during patch on write-only path %v while authed: %v / %v", path, err, resp)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type pathAuthChecker int
 | 
						|
 | 
						|
const (
 | 
						|
	shouldBeAuthed pathAuthChecker = iota
 | 
						|
	shouldBeUnauthedReadList
 | 
						|
	shouldBeUnauthedWriteOnly
 | 
						|
)
 | 
						|
 | 
						|
var pathAuthChckerMap = map[pathAuthChecker]pathAuthCheckerFunc{
 | 
						|
	shouldBeAuthed:            pathShouldBeAuthed,
 | 
						|
	shouldBeUnauthedReadList:  pathShouldBeUnauthedReadList,
 | 
						|
	shouldBeUnauthedWriteOnly: pathShouldBeUnauthedWriteOnly,
 | 
						|
}
 | 
						|
 | 
						|
func TestProperAuthing(t *testing.T) {
 | 
						|
	t.Parallel()
 | 
						|
	coreConfig := &vault.CoreConfig{
 | 
						|
		LogicalBackends: map[string]logical.Factory{
 | 
						|
			"ssh": Factory,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
 | 
						|
		HandlerFunc: vaulthttp.Handler,
 | 
						|
	})
 | 
						|
	cluster.Start()
 | 
						|
	defer cluster.Cleanup()
 | 
						|
	client := cluster.Cores[0].Client
 | 
						|
	token := client.Token()
 | 
						|
 | 
						|
	// Mount SSH.
 | 
						|
	err := client.Sys().MountWithContext(ctx, "ssh", &api.MountInput{
 | 
						|
		Type: "ssh",
 | 
						|
		Config: api.MountConfigInput{
 | 
						|
			DefaultLeaseTTL: "16h",
 | 
						|
			MaxLeaseTTL:     "60h",
 | 
						|
		},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Setup basic configuration.
 | 
						|
	_, err = client.Logical().WriteWithContext(ctx, "ssh/config/ca", map[string]interface{}{
 | 
						|
		"generate_signing_key": true,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = client.Logical().WriteWithContext(ctx, "ssh/roles/test-ca", map[string]interface{}{
 | 
						|
		"key_type":                "ca",
 | 
						|
		"allow_user_certificates": true,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = client.Logical().WriteWithContext(ctx, "ssh/issue/test-ca", map[string]interface{}{
 | 
						|
		"username": "toor",
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = client.Logical().WriteWithContext(ctx, "ssh/roles/test-otp", map[string]interface{}{
 | 
						|
		"key_type":     "otp",
 | 
						|
		"default_user": "toor",
 | 
						|
		"cidr_list":    "127.0.0.0/24",
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err := client.Logical().WriteWithContext(ctx, "ssh/creds/test-otp", map[string]interface{}{
 | 
						|
		"username": "toor",
 | 
						|
		"ip":       "127.0.0.1",
 | 
						|
	})
 | 
						|
	if err != nil || resp == nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	// key := resp.Data["key"].(string)
 | 
						|
 | 
						|
	paths := map[string]pathAuthChecker{
 | 
						|
		"config/ca":          shouldBeAuthed,
 | 
						|
		"config/zeroaddress": shouldBeAuthed,
 | 
						|
		"creds/test-otp":     shouldBeAuthed,
 | 
						|
		"issue/test-ca":      shouldBeAuthed,
 | 
						|
		"lookup":             shouldBeAuthed,
 | 
						|
		"public_key":         shouldBeUnauthedReadList,
 | 
						|
		"roles/test-ca":      shouldBeAuthed,
 | 
						|
		"roles/test-otp":     shouldBeAuthed,
 | 
						|
		"roles":              shouldBeAuthed,
 | 
						|
		"sign/test-ca":       shouldBeAuthed,
 | 
						|
		"tidy/dynamic-keys":  shouldBeAuthed,
 | 
						|
		"verify":             shouldBeUnauthedWriteOnly,
 | 
						|
	}
 | 
						|
	for path, checkerType := range paths {
 | 
						|
		checker := pathAuthChckerMap[checkerType]
 | 
						|
		checker(t, client, "ssh/"+path, token)
 | 
						|
	}
 | 
						|
 | 
						|
	client.SetToken(token)
 | 
						|
	openAPIResp, err := client.Logical().ReadWithContext(ctx, "sys/internal/specs/openapi")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("failed to get openapi data: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if len(openAPIResp.Data["paths"].(map[string]interface{})) == 0 {
 | 
						|
		t.Fatalf("expected to get response from OpenAPI; got empty path list")
 | 
						|
	}
 | 
						|
 | 
						|
	validatedPath := false
 | 
						|
	for openapi_path, raw_data := range openAPIResp.Data["paths"].(map[string]interface{}) {
 | 
						|
		if !strings.HasPrefix(openapi_path, "/ssh/") {
 | 
						|
			t.Logf("Skipping path: %v", openapi_path)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		t.Logf("Validating path: %v", openapi_path)
 | 
						|
		validatedPath = true
 | 
						|
 | 
						|
		// Substitute values in from our testing map.
 | 
						|
		raw_path := openapi_path[5:]
 | 
						|
		if strings.Contains(raw_path, "{role}") && strings.Contains(raw_path, "roles/") {
 | 
						|
			raw_path = strings.ReplaceAll(raw_path, "{role}", "test-ca")
 | 
						|
		}
 | 
						|
		if strings.Contains(raw_path, "{role}") && (strings.Contains(raw_path, "sign/") || strings.Contains(raw_path, "issue/")) {
 | 
						|
			raw_path = strings.ReplaceAll(raw_path, "{role}", "test-ca")
 | 
						|
		}
 | 
						|
		if strings.Contains(raw_path, "{role}") && strings.Contains(raw_path, "creds") {
 | 
						|
			raw_path = strings.ReplaceAll(raw_path, "{role}", "test-otp")
 | 
						|
		}
 | 
						|
 | 
						|
		handler, present := paths[raw_path]
 | 
						|
		if !present {
 | 
						|
			t.Fatalf("OpenAPI reports SSH mount contains %v->%v  but was not tested to be authed or authed.", openapi_path, raw_path)
 | 
						|
		}
 | 
						|
 | 
						|
		openapi_data := raw_data.(map[string]interface{})
 | 
						|
		hasList := false
 | 
						|
		rawGetData, hasGet := openapi_data["get"]
 | 
						|
		if hasGet {
 | 
						|
			getData := rawGetData.(map[string]interface{})
 | 
						|
			getParams, paramsPresent := getData["parameters"].(map[string]interface{})
 | 
						|
			if getParams != nil && paramsPresent {
 | 
						|
				if _, hasList = getParams["list"]; hasList {
 | 
						|
					// LIST is exclusive from GET on the same endpoint usually.
 | 
						|
					hasGet = false
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		_, hasPost := openapi_data["post"]
 | 
						|
		_, hasDelete := openapi_data["delete"]
 | 
						|
 | 
						|
		if handler == shouldBeUnauthedReadList {
 | 
						|
			if hasPost || hasDelete {
 | 
						|
				t.Fatalf("Unauthed read-only endpoints should not have POST/DELETE capabilities")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !validatedPath {
 | 
						|
		t.Fatalf("Expected to have validated at least one path.")
 | 
						|
	}
 | 
						|
}
 |