@@ -19,8 +19,10 @@ import (
1919
2020 "golang.org/x/crypto/ssh"
2121
22+ "github.com/hashicorp/vault/builtin/credential/userpass"
2223 "github.com/hashicorp/vault/helper/testhelpers/docker"
2324 logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
25+ vaulthttp "github.com/hashicorp/vault/http"
2426 "github.com/hashicorp/vault/vault"
2527 "github.com/mitchellh/mapstructure"
2628)
@@ -122,6 +124,7 @@ SjOQL/GkH1nkRcDS9++aAAAAAmNhAQID
122124
123125 dockerImageTagSupportsRSA1 = "8.1_p1-r0-ls20"
124126 dockerImageTagSupportsNoRSA1 = "8.4_p1-r3-ls48"
127+
125128)
126129
127130func prepareTestContainer (t * testing.T , tag , caPublicKeyPEM string ) (func (), string ) {
@@ -158,7 +161,7 @@ func prepareTestContainer(t *testing.T, tag, caPublicKeyPEM string) (func(), str
158161
159162 // Install util-linux for non-busybox flock that supports timeout option
160163 err = testSSH ("vaultssh" , sshAddress , ssh .PublicKeys (signer ), fmt .Sprintf (`
161- set -e;
164+ set -e;
162165 sudo ln -s /config /home/vaultssh
163166 sudo apk add util-linux;
164167 echo "LogLevel DEBUG" | sudo tee -a /config/ssh_host_keys/sshd_config;
@@ -1318,6 +1321,252 @@ func TestBackend_DisallowUserProvidedKeyIDs(t *testing.T) {
13181321 logicaltest .Test (t , testCase )
13191322}
13201323
1324+ func TestBackend_DefExtTemplatingEnabled (t * testing.T ) {
1325+ cluster , userpassToken := getSshCaTestCluster (t , testUserName )
1326+ defer cluster .Cleanup ()
1327+ client := cluster .Cores [0 ].Client
1328+
1329+ // Get auth accessor for identity template.
1330+ auths , err := client .Sys ().ListAuth ()
1331+ if err != nil {
1332+ t .Fatal (err )
1333+ }
1334+ userpassAccessor := auths ["userpass/" ].Accessor
1335+
1336+ // Write SSH role.
1337+ _ , err = client .Logical ().Write ("ssh/roles/test" , map [string ]interface {}{
1338+ "key_type" : "ca" ,
1339+ "allowed_extensions" : "login@zipzap.com" ,
1340+ "allow_user_certificates" : true ,
1341+ "allowed_users" : "tuber" ,
1342+ "default_user" : "tuber" ,
1343+ "default_extensions_template" : true ,
1344+ "default_extensions" : map [string ]interface {}{
1345+ "login@foobar.com" : "{{identity.entity.aliases." + userpassAccessor + ".name}}" ,
1346+ },
1347+ })
1348+ if err != nil {
1349+ t .Fatal (err )
1350+ }
1351+
1352+ sshKeyID := "vault-userpass-" + testUserName + "-9bd0f01b7dfc50a13aa5e5cd11aea19276968755c8f1f9c98965d04147f30ed0"
1353+
1354+ // Issue SSH certificate with default extensions templating enabled, and no user-provided extensions
1355+ client .SetToken (userpassToken )
1356+ resp , err := client .Logical ().Write ("ssh/sign/test" , map [string ]interface {}{
1357+ "public_key" : publicKey4096 ,
1358+ })
1359+ if err != nil {
1360+ t .Fatal (err )
1361+ }
1362+ signedKey := resp .Data ["signed_key" ].(string )
1363+ key , _ := base64 .StdEncoding .DecodeString (strings .Split (signedKey , " " )[1 ])
1364+
1365+ parsedKey , err := ssh .ParsePublicKey (key )
1366+ if err != nil {
1367+ t .Fatal (err )
1368+ }
1369+
1370+ defaultExtensionPermissions := map [string ]string {
1371+ "login@foobar.com" : testUserName ,
1372+ }
1373+
1374+ err = validateSSHCertificate (parsedKey .(* ssh.Certificate ), sshKeyID , ssh .UserCert , []string {"tuber" }, map [string ]string {}, defaultExtensionPermissions , 16 * time .Hour )
1375+ if err != nil {
1376+ t .Fatal (err )
1377+ }
1378+
1379+ // Issue SSH certificate with default extensions templating enabled, and user-provided extensions
1380+ // The certificate should only have the user-provided extensions, and no templated extensions
1381+ userProvidedExtensionPermissions := map [string ]string {
1382+ "login@zipzap.com" : "some_other_user_name" ,
1383+ }
1384+ resp , err = client .Logical ().Write ("ssh/sign/test" , map [string ]interface {}{
1385+ "public_key" : publicKey4096 ,
1386+ "extensions" : userProvidedExtensionPermissions ,
1387+ })
1388+ if err != nil {
1389+ t .Fatal (err )
1390+ }
1391+ signedKey = resp .Data ["signed_key" ].(string )
1392+ key , _ = base64 .StdEncoding .DecodeString (strings .Split (signedKey , " " )[1 ])
1393+
1394+ parsedKey , err = ssh .ParsePublicKey (key )
1395+ if err != nil {
1396+ t .Fatal (err )
1397+ }
1398+
1399+ err = validateSSHCertificate (parsedKey .(* ssh.Certificate ), sshKeyID , ssh .UserCert , []string {"tuber" }, map [string ]string {}, userProvidedExtensionPermissions , 16 * time .Hour )
1400+ if err != nil {
1401+ t .Fatal (err )
1402+ }
1403+
1404+ // Issue SSH certificate with default extensions templating enabled, and invalid user-provided extensions - it should fail
1405+ invalidUserProvidedExtensionPermissions := map [string ]string {
1406+ "login@foobar.com" : "{{identity.entity.metadata}}" ,
1407+ }
1408+ resp , err = client .Logical ().Write ("ssh/sign/test" , map [string ]interface {}{
1409+ "public_key" : publicKey4096 ,
1410+ "extensions" : invalidUserProvidedExtensionPermissions ,
1411+ })
1412+ if err == nil {
1413+ t .Fatal ("expected an error while attempting to sign a key with invalid permissions" )
1414+ }
1415+ }
1416+
1417+ func TestBackend_DefExtTemplatingDisabled (t * testing.T ) {
1418+ cluster , userpassToken := getSshCaTestCluster (t , testUserName )
1419+ defer cluster .Cleanup ()
1420+ client := cluster .Cores [0 ].Client
1421+
1422+ // Get auth accessor for identity template.
1423+ auths , err := client .Sys ().ListAuth ()
1424+ if err != nil {
1425+ t .Fatal (err )
1426+ }
1427+ userpassAccessor := auths ["userpass/" ].Accessor
1428+
1429+ // Write SSH role to test with any extension. We also provide a templated default extension,
1430+ // to verify that it's not actually being evaluated
1431+ _ , err = client .Logical ().Write ("ssh/roles/test_allow_all_extensions" , map [string ]interface {}{
1432+ "key_type" : "ca" ,
1433+ "allow_user_certificates" : true ,
1434+ "allowed_users" : "tuber" ,
1435+ "default_user" : "tuber" ,
1436+ "default_extensions_template" : false ,
1437+ "default_extensions" : map [string ]interface {}{
1438+ "login@foobar.com" : "{{identity.entity.aliases." + userpassAccessor + ".name}}" ,
1439+ },
1440+ })
1441+ if err != nil {
1442+ t .Fatal (err )
1443+ }
1444+
1445+ sshKeyID := "vault-userpass-" + testUserName + "-9bd0f01b7dfc50a13aa5e5cd11aea19276968755c8f1f9c98965d04147f30ed0"
1446+
1447+ // Issue SSH certificate with default extensions templating disabled, and no user-provided extensions
1448+ client .SetToken (userpassToken )
1449+ defaultExtensionPermissions := map [string ]string {
1450+ "login@foobar.com" : "{{identity.entity.aliases." + userpassAccessor + ".name}}" ,
1451+ "login@zipzap.com" : "some_other_user_name" ,
1452+ }
1453+ resp , err := client .Logical ().Write ("ssh/sign/test_allow_all_extensions" , map [string ]interface {}{
1454+ "public_key" : publicKey4096 ,
1455+ "extensions" : defaultExtensionPermissions ,
1456+ })
1457+ if err != nil {
1458+ t .Fatal (err )
1459+ }
1460+ signedKey := resp .Data ["signed_key" ].(string )
1461+ key , _ := base64 .StdEncoding .DecodeString (strings .Split (signedKey , " " )[1 ])
1462+
1463+ parsedKey , err := ssh .ParsePublicKey (key )
1464+ if err != nil {
1465+ t .Fatal (err )
1466+ }
1467+
1468+ err = validateSSHCertificate (parsedKey .(* ssh.Certificate ), sshKeyID , ssh .UserCert , []string {"tuber" }, map [string ]string {}, defaultExtensionPermissions , 16 * time .Hour )
1469+ if err != nil {
1470+ t .Fatal (err )
1471+ }
1472+
1473+ // Issue SSH certificate with default extensions templating disabled, and user-provided extensions
1474+ client .SetToken (userpassToken )
1475+ userProvidedAnyExtensionPermissions := map [string ]string {
1476+ "login@foobar.com" : "not_userpassname" ,
1477+ "login@zipzap.com" : "some_other_user_name" ,
1478+ }
1479+ resp , err = client .Logical ().Write ("ssh/sign/test_allow_all_extensions" , map [string ]interface {}{
1480+ "public_key" : publicKey4096 ,
1481+ "extensions" : userProvidedAnyExtensionPermissions ,
1482+ })
1483+ if err != nil {
1484+ t .Fatal (err )
1485+ }
1486+ signedKey = resp .Data ["signed_key" ].(string )
1487+ key , _ = base64 .StdEncoding .DecodeString (strings .Split (signedKey , " " )[1 ])
1488+
1489+ parsedKey , err = ssh .ParsePublicKey (key )
1490+ if err != nil {
1491+ t .Fatal (err )
1492+ }
1493+
1494+ err = validateSSHCertificate (parsedKey .(* ssh.Certificate ), sshKeyID , ssh .UserCert , []string {"tuber" }, map [string ]string {}, userProvidedAnyExtensionPermissions , 16 * time .Hour )
1495+ if err != nil {
1496+ t .Fatal (err )
1497+ }
1498+ }
1499+
1500+ func getSshCaTestCluster (t * testing.T , userIdentity string ) (* vault.TestCluster , string ) {
1501+ coreConfig := & vault.CoreConfig {
1502+ CredentialBackends : map [string ]logical.Factory {
1503+ "userpass" : userpass .Factory ,
1504+ },
1505+ LogicalBackends : map [string ]logical.Factory {
1506+ "ssh" : Factory ,
1507+ },
1508+ }
1509+ cluster := vault .NewTestCluster (t , coreConfig , & vault.TestClusterOptions {
1510+ HandlerFunc : vaulthttp .Handler ,
1511+ })
1512+ cluster .Start ()
1513+ client := cluster .Cores [0 ].Client
1514+
1515+ // Write test policy for userpass auth method.
1516+ err := client .Sys ().PutPolicy ("test" , `
1517+ path "ssh/*" {
1518+ capabilities = ["update"]
1519+ }` )
1520+ if err != nil {
1521+ t .Fatal (err )
1522+ }
1523+
1524+ // Enable userpass auth method.
1525+ if err := client .Sys ().EnableAuth ("userpass" , "userpass" , "" ); err != nil {
1526+ t .Fatal (err )
1527+ }
1528+
1529+ // Configure test role for userpass.
1530+ if _ , err := client .Logical ().Write ("auth/userpass/users/" + userIdentity , map [string ]interface {}{
1531+ "password" : "test" ,
1532+ "policies" : "test" ,
1533+ }); err != nil {
1534+ t .Fatal (err )
1535+ }
1536+
1537+ // Login userpass for test role and keep client token.
1538+ secret , err := client .Logical ().Write ("auth/userpass/login/" + userIdentity , map [string ]interface {}{
1539+ "password" : "test" ,
1540+ })
1541+ if err != nil || secret == nil {
1542+ t .Fatal (err )
1543+ }
1544+ userpassToken := secret .Auth .ClientToken
1545+
1546+ // Mount SSH.
1547+ err = client .Sys ().Mount ("ssh" , & api.MountInput {
1548+ Type : "ssh" ,
1549+ Config : api.MountConfigInput {
1550+ DefaultLeaseTTL : "16h" ,
1551+ MaxLeaseTTL : "60h" ,
1552+ },
1553+ })
1554+ if err != nil {
1555+ t .Fatal (err )
1556+ }
1557+
1558+ // Configure SSH CA.
1559+ _ , err = client .Logical ().Write ("ssh/config/ca" , map [string ]interface {}{
1560+ "public_key" : testCAPublicKey ,
1561+ "private_key" : testCAPrivateKey ,
1562+ })
1563+ if err != nil {
1564+ t .Fatal (err )
1565+ }
1566+
1567+ return cluster , userpassToken
1568+ }
1569+
13211570func configCaStep (caPublicKey , caPrivateKey string ) logicaltest.TestStep {
13221571 return logicaltest.TestStep {
13231572 Operation : logical .UpdateOperation ,
@@ -1391,7 +1640,7 @@ func validateSSHCertificate(cert *ssh.Certificate, keyID string, certType int, v
13911640
13921641 actualTTL := time .Unix (int64 (cert .ValidBefore ), 0 ).Add (- 30 * time .Second ).Sub (time .Unix (int64 (cert .ValidAfter ), 0 ))
13931642 if actualTTL != ttl {
1394- return fmt .Errorf ("incorrect ttl: expected: %v, actualL %v" , ttl , actualTTL )
1643+ return fmt .Errorf ("incorrect ttl: expected: %v, actual %v" , ttl , actualTTL )
13951644 }
13961645
13971646 if ! reflect .DeepEqual (cert .ValidPrincipals , validPrincipals ) {
0 commit comments