diff --git a/Makefile b/Makefile index fe31e43c94003..be67b30499c5d 100644 --- a/Makefile +++ b/Makefile @@ -26,13 +26,13 @@ LIBS = $(shell find lib -type f -name '*.go') *.go .PHONY: all all: $(VERSRC) $(BINARIES) -$(BUILDDIR)/tctl: $(LIBS) $(TOOLS) tool/tctl/*.go +$(BUILDDIR)/tctl: $(LIBS) $(TOOLS) tool/tctl/common/*.go tool/tctl/*go go build -o $(BUILDDIR)/tctl -i $(BUILDFLAGS) ./tool/tctl $(BUILDDIR)/teleport: $(LIBS) tool/teleport/*.go tool/teleport/common/*.go go build -o $(BUILDDIR)/teleport -i $(BUILDFLAGS) ./tool/teleport -$(BUILDDIR)/tsh: $(LIBS) tool/tsh/*.go +$(BUILDDIR)/tsh: $(LIBS) tool/tsh/*.go tool/tsh/common/*go go build -o $(BUILDDIR)/tsh -i $(BUILDFLAGS) ./tool/tsh .PHONY: goinstall diff --git a/fixtures/certs/identities/ca.pem b/fixtures/certs/identities/ca.pem new file mode 100644 index 0000000000000..b2625d4d60dc0 --- /dev/null +++ b/fixtures/certs/identities/ca.pem @@ -0,0 +1 @@ +@cert-authority *.turing.local ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEk4cVIiydp9xSPIb8UqXpShY8zPlk/lpR69UL+0+RnNXtQl7GcQUZsrXDB2gOCfj+doKZj8Pt8oQVSDJF/vKhr+KS2Z+LC2Gyt8D5IY/acyyhSN5VoIo0JzIOr5CPGJNpLChREFuveV30hLihSfY52cqSvu7N5u34BlZ29WTLeBD9WssAG5HZUES8Xo3neHBl4SOck+mdiUvOIPhcnPiYRmYltOI3GJRu5y1xGemoPU3MnMziQMqnKCc2+To6IC8CkeQqa8D//BxLjenjSgn1K/SLUHraMb5qCmf77fyshj6A9jamgo0UOaOqem+jyg8idnz6JbVfXwW0nEaSyPzX type=host diff --git a/fixtures/certs/identities/cert-key.pem b/fixtures/certs/identities/cert-key.pem new file mode 100644 index 0000000000000..bf0a7a014d9cf --- /dev/null +++ b/fixtures/certs/identities/cert-key.pem @@ -0,0 +1,28 @@ +ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg0NeCnpO5ZAzWmMX6XwjrFyDi+JRrPLNb0vrEYqJp+bEAAAADAQABAAABAQC96eyjDJkj80k2JJ2imXQTXb4VfjEXHxPClX4uw0Th7dJ6NxvKb+AfAbaFdYu3xJjyUhFkHg0hPtMhe/ubq0wrejkTtYwd87iWj8wu+aiSziRexphXClxNt8RWv+1mgAVZuBSPHg4jykrYzpaQOqmIiOcMBpumFpwA2cXNRgLbEdZ4uwpNjBYwxGigh1m50OiFvcXFvrwvGkkqDwExIaCqSoK+E3NmTLt6I5eTVvjdhxSzKqwF65vY9XWqh4w1JP2NHCQSkyh2rlC4WM0mpkyL4ZmJdIsRFj1DxN7Ovma6HS8AKJeiDyShuWounwCsoK33onjr+ib9cYUAvsKTdB9pAAAAAAAAAAAAAAABAAAACmVrb250c2V2b3kAAAAOAAAACmVrb250c2V2b3kAAAAAAAAAAAAAAABZPPDKAAAAAAAAAHYAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOdGVsZXBvcnQtcm9sZXMAAAAwAAAALHsidmVyc2lvbiI6InYxIiwicm9sZXMiOlsidXNlcjpla29udHNldm95Il19AAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAMA/0pVkfFhDPUDosZpM9nP/r/t6tORkmhXCxMkLZiS7+kg0htYSDLmwFGfzgSbYAH6Bryu9BZOxv1W23WW9oW7IdJpwfCpuyzFoRN0/2mHhAAHETtxucksTgYNwN+dDXF/IzG/QGVYswP4ENte4ZuNsd3bquBu+opK7CXU4B+UtsY0JUcV7gU5TzCZBdFpzLgB2VUpiHlFg0PUuV74aZmwzHlwoONBSIn2FZpHmvN2ZUdqTHSjof1vgH20cScMWGk05dFuM5gWjHEYC1gwdPpmTGcgN93SAQwKiAUQ6ZnJ+lVhzSp+/vxVz/aecDnrza+xI26DnB/nEEiCMu92WYxEAAAEPAAAAB3NzaC1yc2EAAAEAQRUml83QKsEeWB0WswfR1rvEzzumYRn/CAMTSGsF99bNzHmZ0lJbwCzNdl0hlJ3tGVPhANL5WwWuiLN1q6O8qrUU4cGJK3L8eNFUXmJIVc1xH2bIaws+nHikqPHnxbtAzJBbHeCngBX7eVT69bW6AdgWSHSzlPRaAaApMoEwVIMKOLiedjy7D9s/Cd+GtOtxTMoG/LmFBnvUiuXWwiQ658MRrg65ATl0x24ErJqz2cnj52Sy5G6SNUrENRqkP8TxRtp6a+FT1oJQ/2LqLwnlPQpb41j6fDqLRa47NU2TRnYRv0rhCMHO5tIhA51qhRlU9R2m5BH5o1JswN/phMgbjg== +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAvensowyZI/NJNiSdopl0E12+FX4xFx8TwpV+LsNE4e3Sejcb +ym/gHwG2hXWLt8SY8lIRZB4NIT7TIXv7m6tMK3o5E7WMHfO4lo/MLvmoks4kXsaY +VwpcTbfEVr/tZoAFWbgUjx4OI8pK2M6WkDqpiIjnDAabphacANnFzUYC2xHWeLsK +TYwWMMRooIdZudDohb3Fxb68LxpJKg8BMSGgqkqCvhNzZky7eiOXk1b43YcUsyqs +Beub2PV1qoeMNST9jRwkEpModq5QuFjNJqZMi+GZiXSLERY9Q8Tezr5muh0vACiX +og8koblqLp8ArKCt96J46/om/XGFAL7Ck3QfaQIDAQABAoIBAQC8/4DNXytEWMiC +RnxOJhMbds5Fy3kFPptGqcmStifmA+zUTeWtWBseIHFJbgqmztM7TKscDMAaVtB8 +4Usrx5SdLByDXchcwoDv7ZlRIoo910Lgwxk2fgwQGBMgFg8nU75/ZC+pokqGGbrU ++vth+89eHoh5MlZSOuvz+MXeHI+Y/Pep5l6reqV3c4EqvncV8Ws31+wNRlpJJZ5a +hpJhwJTkFW9Ry/ifQ7Ub/mYOrXKe1Y8eak6zFMab//8t9zxSTzCPwNrXMx8AzCZy +G3TBw2UEkU2dUFH3/5hg7tgdE+xPYH/6tGozmE1luK+LRIJ/bd9uW7sSGXQq8bGB +u/M9LmeRAoGBANz+Rvhzl5cjjvUpjLcsu2tGjWK5uAvbcHTQd8bjHQINtKTQREFo +CoEA8DD6qiyhgwuH7BIVNjjOr9LcMjuorgbcVTOBVM5RLG7KUUPCDDejlmLI+s9w +8VKv6Vx8bt8901IUsIhNJtfoZOXEBbJVDsU3iD5zFrA9dj2EQNWG2D8lAoGBANv/ +T4NMs174oyrZYRTHOoepvlvX9zm9Ptcz9P7GcdZbg3jkQSXKAZqUwczDT8FzdxXH +ycwaCsVpikEPGJaKCnhSV1Kh2tR8H05kOxPy3vzpzsFtfrFyTPFzydOES//A+ZHD +ydcNph1K8Ztx+1YzTOj9KjOyuRpb5kUvO2c4zp31AoGBAK9HTusIY5eQsHZq+hze +8dfoIYPIYd2lstAz+Ixa3kseq8R9G2X1Kz+eiuOOLSMxB0tCB09gW5068eGAnKcM +5tqyLzGmxqjNYTyOY14mrqICseiwF54oqn823xRn7VhLJSzZFBtHdiORQ1Wp4ArN +w+VQYlOF3Nz0IrAwEWxKg4GxAoGBANlBOHShukF/qSMXqRer59ExgBuTG0KZ8QT0 ++mzf7GuT1DH+t5dp9kuBvCFKf+i67k9EDbTRwvFRWIcHMXD4wX4xUqr3y/Mq4H+5 +293Haw64lsXOK99w0Stg/V80txjKqauZfioyAGnNKOwpk9t8reconBSR2tp9Btor +2q4FG4ZBAoGANLnC8I3eHWSj3/ME+tOCu7EiiDtiE2Kt1UEtreQGyeF/cPBsDiSF +3XxgYO9bCh0TGX52e4Bfffra5f1Hvgw84dESQWunYJByb19a737MI8RN+RHKjaSo +rBjbUpA16Fi8NSro/mXDLCh8mTzu0tPG+e1jqcEVc5JDLYIau12j6jw= +-----END RSA PRIVATE KEY----- diff --git a/fixtures/certs/identities/key b/fixtures/certs/identities/key new file mode 100644 index 0000000000000..8ce739f317ce0 --- /dev/null +++ b/fixtures/certs/identities/key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA2CJktK9jHIrhDEawPr+ougXb6YqoBH6u/iuYcS4CsenvA0No +xlfK2g0JsnfzQmmrLaxAXOFejDfTMzjcC41AC1ev1/cOYO/9daz3HxwktmpNn3FN +krF+XCEvCJwXBqy4n+ZoW3vS5pUb/9CyiGADv9QCQrZuoUjvKX2keI3TSpz7gI+m +Xj/DN5/vZDx9W4cfDXlDryZBMHK/JT/3EpMeCVGOr0W7NqOMvfJZt/G/Ai4MSGBZ +RScmwYYDw/2tjKaCWC4PRxpmzVyrrh3k+LK1kYtCFtSRaxmyrOYeUOk+ew2M19l6 +blDx42Xx5y2M1a8NQ2HOP0dCVKcGSJ3Q/B8mxwIDAQABAoIBAQCzQdtKbIiEPL8o +Ylx8vpMfLgrVqLVvfO6ASgIWJLYBf2dHypnUny3UKaoYRhoQw/lAWTPMlPLI0ugs +/ISsZAtxHNnwAa0AQytxdPJE8B+W15Xnnp5bAzEtEiyjNGp4k7pQjFWTQqCJs7PU +OPBJ4XBaomj5kbsxs38AZ3+IqQBMSV2HW0HB01xrO3G5s1i5SnNbU+Vuyl4vo6gm +wUhrdpPGr6lsaeO3bODCO+rcDfam3HYQmbEKnUa34gpmNXoJLeLJmSdlyhv2jsJX +YFlZvegFE1BCoaU5nvwHa0MhitnL4sN0iCZgIzAruFPJ9Bvl0oJ973srpf99gKM7 +oq4him4hAoGBANmxrYqwHWwiOZfEk93FcmFxg6lIrgpCIZ8hvjnF3gAa3NGOQepg +i575m70RY4yU3C35+WbaEBAXl9GLcvaK8enoIXchiWqYk9JtltyZXJlPwY76dJZt +T4/icwHFyvgDqd/xEs6eh36SApcgWfPFddgDequV6DvDQOhsPpFmBwARAoGBAP4q +dNrYUuMtiIff6fItuvgf4kB0U6JDFFsTvBG5Po1E9pFplh6lIyHpv+c9T8P6lZho +fGzbY35i8Ov7lqcrVxtEsiqmPWR6QBnHtEuChtC4vb6hl2kV/MlYc+tC4APd23xg +DzYqLdVOJ6RB3rfodPbVYvu01+pkR1NKvwI57RFXAoGBAIHWp1sAj4vfHdqXNFoh +WYck3RIqdyNHLiZrSbnLeg01+F5EKqxPyPaYiXrK1EUUw/3oCgh+JvZyG+qu8XJ6 +jK6l9M/JANzDA+eN1VzdW41VNGzClKbjq4B134I/Jj+mb7tRXZY+lzG2hDT+5qeu +LgsYiCGu10RNwHjflrHB2IsBAoGAXLZb/eBfC+N2JCo3ilHIG+51d5F3WH8jk711 +IvnxqVJ4pr5fNjqCwEIl8FHbIN/tZbTnfkXg2x94Rnx1jfEvSxEZ9JjDWD2H2F+S +kuDAEK7y9/C17G1K0p9jWXQBhyeMgqf/pIBqS57AsxgcB/XRhKB+BNcI08VUhzuC +xWsf4O0CgYEA0jdKH7Nr+G0kHGb79p3/fEM0H43sdyLXmudXXrym0wDbLNKBY6ny +k+kA63rxsMCKTPIAHcj77Mjw2MnjEwsBKZtNhewr1fv5GgqXI3G7WLKe6W9RFhbj +RMndSXGdA3J8ZJ18CF/pbGTGAhGUx2KQvHIFcXoW23fjqnHMy2mqWWI= +-----END RSA PRIVATE KEY----- diff --git a/fixtures/certs/identities/key-cert-ca.pem b/fixtures/certs/identities/key-cert-ca.pem new file mode 100644 index 0000000000000..4ca7099574703 --- /dev/null +++ b/fixtures/certs/identities/key-cert-ca.pem @@ -0,0 +1,29 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAvensowyZI/NJNiSdopl0E12+FX4xFx8TwpV+LsNE4e3Sejcb +ym/gHwG2hXWLt8SY8lIRZB4NIT7TIXv7m6tMK3o5E7WMHfO4lo/MLvmoks4kXsaY +VwpcTbfEVr/tZoAFWbgUjx4OI8pK2M6WkDqpiIjnDAabphacANnFzUYC2xHWeLsK +TYwWMMRooIdZudDohb3Fxb68LxpJKg8BMSGgqkqCvhNzZky7eiOXk1b43YcUsyqs +Beub2PV1qoeMNST9jRwkEpModq5QuFjNJqZMi+GZiXSLERY9Q8Tezr5muh0vACiX +og8koblqLp8ArKCt96J46/om/XGFAL7Ck3QfaQIDAQABAoIBAQC8/4DNXytEWMiC +RnxOJhMbds5Fy3kFPptGqcmStifmA+zUTeWtWBseIHFJbgqmztM7TKscDMAaVtB8 +4Usrx5SdLByDXchcwoDv7ZlRIoo910Lgwxk2fgwQGBMgFg8nU75/ZC+pokqGGbrU ++vth+89eHoh5MlZSOuvz+MXeHI+Y/Pep5l6reqV3c4EqvncV8Ws31+wNRlpJJZ5a +hpJhwJTkFW9Ry/ifQ7Ub/mYOrXKe1Y8eak6zFMab//8t9zxSTzCPwNrXMx8AzCZy +G3TBw2UEkU2dUFH3/5hg7tgdE+xPYH/6tGozmE1luK+LRIJ/bd9uW7sSGXQq8bGB +u/M9LmeRAoGBANz+Rvhzl5cjjvUpjLcsu2tGjWK5uAvbcHTQd8bjHQINtKTQREFo +CoEA8DD6qiyhgwuH7BIVNjjOr9LcMjuorgbcVTOBVM5RLG7KUUPCDDejlmLI+s9w +8VKv6Vx8bt8901IUsIhNJtfoZOXEBbJVDsU3iD5zFrA9dj2EQNWG2D8lAoGBANv/ +T4NMs174oyrZYRTHOoepvlvX9zm9Ptcz9P7GcdZbg3jkQSXKAZqUwczDT8FzdxXH +ycwaCsVpikEPGJaKCnhSV1Kh2tR8H05kOxPy3vzpzsFtfrFyTPFzydOES//A+ZHD +ydcNph1K8Ztx+1YzTOj9KjOyuRpb5kUvO2c4zp31AoGBAK9HTusIY5eQsHZq+hze +8dfoIYPIYd2lstAz+Ixa3kseq8R9G2X1Kz+eiuOOLSMxB0tCB09gW5068eGAnKcM +5tqyLzGmxqjNYTyOY14mrqICseiwF54oqn823xRn7VhLJSzZFBtHdiORQ1Wp4ArN +w+VQYlOF3Nz0IrAwEWxKg4GxAoGBANlBOHShukF/qSMXqRer59ExgBuTG0KZ8QT0 ++mzf7GuT1DH+t5dp9kuBvCFKf+i67k9EDbTRwvFRWIcHMXD4wX4xUqr3y/Mq4H+5 +293Haw64lsXOK99w0Stg/V80txjKqauZfioyAGnNKOwpk9t8reconBSR2tp9Btor +2q4FG4ZBAoGANLnC8I3eHWSj3/ME+tOCu7EiiDtiE2Kt1UEtreQGyeF/cPBsDiSF +3XxgYO9bCh0TGX52e4Bfffra5f1Hvgw84dESQWunYJByb19a737MI8RN+RHKjaSo +rBjbUpA16Fi8NSro/mXDLCh8mTzu0tPG+e1jqcEVc5JDLYIau12j6jw= +-----END RSA PRIVATE KEY----- +ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg0NeCnpO5ZAzWmMX6XwjrFyDi+JRrPLNb0vrEYqJp+bEAAAADAQABAAABAQC96eyjDJkj80k2JJ2imXQTXb4VfjEXHxPClX4uw0Th7dJ6NxvKb+AfAbaFdYu3xJjyUhFkHg0hPtMhe/ubq0wrejkTtYwd87iWj8wu+aiSziRexphXClxNt8RWv+1mgAVZuBSPHg4jykrYzpaQOqmIiOcMBpumFpwA2cXNRgLbEdZ4uwpNjBYwxGigh1m50OiFvcXFvrwvGkkqDwExIaCqSoK+E3NmTLt6I5eTVvjdhxSzKqwF65vY9XWqh4w1JP2NHCQSkyh2rlC4WM0mpkyL4ZmJdIsRFj1DxN7Ovma6HS8AKJeiDyShuWounwCsoK33onjr+ib9cYUAvsKTdB9pAAAAAAAAAAAAAAABAAAACmVrb250c2V2b3kAAAAOAAAACmVrb250c2V2b3kAAAAAAAAAAAAAAABZPPDKAAAAAAAAAHYAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOdGVsZXBvcnQtcm9sZXMAAAAwAAAALHsidmVyc2lvbiI6InYxIiwicm9sZXMiOlsidXNlcjpla29udHNldm95Il19AAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAMA/0pVkfFhDPUDosZpM9nP/r/t6tORkmhXCxMkLZiS7+kg0htYSDLmwFGfzgSbYAH6Bryu9BZOxv1W23WW9oW7IdJpwfCpuyzFoRN0/2mHhAAHETtxucksTgYNwN+dDXF/IzG/QGVYswP4ENte4ZuNsd3bquBu+opK7CXU4B+UtsY0JUcV7gU5TzCZBdFpzLgB2VUpiHlFg0PUuV74aZmwzHlwoONBSIn2FZpHmvN2ZUdqTHSjof1vgH20cScMWGk05dFuM5gWjHEYC1gwdPpmTGcgN93SAQwKiAUQ6ZnJ+lVhzSp+/vxVz/aecDnrza+xI26DnB/nEEiCMu92WYxEAAAEPAAAAB3NzaC1yc2EAAAEAQRUml83QKsEeWB0WswfR1rvEzzumYRn/CAMTSGsF99bNzHmZ0lJbwCzNdl0hlJ3tGVPhANL5WwWuiLN1q6O8qrUU4cGJK3L8eNFUXmJIVc1xH2bIaws+nHikqPHnxbtAzJBbHeCngBX7eVT69bW6AdgWSHSzlPRaAaApMoEwVIMKOLiedjy7D9s/Cd+GtOtxTMoG/LmFBnvUiuXWwiQ658MRrg65ATl0x24ErJqz2cnj52Sy5G6SNUrENRqkP8TxRtp6a+FT1oJQ/2LqLwnlPQpb41j6fDqLRa47NU2TRnYRv0rhCMHO5tIhA51qhRlU9R2m5BH5o1JswN/phMgbjg== +@cert-authority *.turing.local ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEk4cVIiydp9xSPIb8UqXpShY8zPlk/lpR69UL+0+RnNXtQl7GcQUZsrXDB2gOCfj+doKZj8Pt8oQVSDJF/vKhr+KS2Z+LC2Gyt8D5IY/acyyhSN5VoIo0JzIOr5CPGJNpLChREFuveV30hLihSfY52cqSvu7N5u34BlZ29WTLeBD9WssAG5HZUES8Xo3neHBl4SOck+mdiUvOIPhcnPiYRmYltOI3GJRu5y1xGemoPU3MnMziQMqnKCc2+To6IC8CkeQqa8D//BxLjenjSgn1K/SLUHraMb5qCmf77fyshj6A9jamgo0UOaOqem+jyg8idnz6JbVfXwW0nEaSyPzX type=host diff --git a/fixtures/certs/identities/key-cert.pem b/fixtures/certs/identities/key-cert.pem new file mode 100644 index 0000000000000..99c45ccce3ee5 --- /dev/null +++ b/fixtures/certs/identities/key-cert.pem @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAvensowyZI/NJNiSdopl0E12+FX4xFx8TwpV+LsNE4e3Sejcb +ym/gHwG2hXWLt8SY8lIRZB4NIT7TIXv7m6tMK3o5E7WMHfO4lo/MLvmoks4kXsaY +VwpcTbfEVr/tZoAFWbgUjx4OI8pK2M6WkDqpiIjnDAabphacANnFzUYC2xHWeLsK +TYwWMMRooIdZudDohb3Fxb68LxpJKg8BMSGgqkqCvhNzZky7eiOXk1b43YcUsyqs +Beub2PV1qoeMNST9jRwkEpModq5QuFjNJqZMi+GZiXSLERY9Q8Tezr5muh0vACiX +og8koblqLp8ArKCt96J46/om/XGFAL7Ck3QfaQIDAQABAoIBAQC8/4DNXytEWMiC +RnxOJhMbds5Fy3kFPptGqcmStifmA+zUTeWtWBseIHFJbgqmztM7TKscDMAaVtB8 +4Usrx5SdLByDXchcwoDv7ZlRIoo910Lgwxk2fgwQGBMgFg8nU75/ZC+pokqGGbrU ++vth+89eHoh5MlZSOuvz+MXeHI+Y/Pep5l6reqV3c4EqvncV8Ws31+wNRlpJJZ5a +hpJhwJTkFW9Ry/ifQ7Ub/mYOrXKe1Y8eak6zFMab//8t9zxSTzCPwNrXMx8AzCZy +G3TBw2UEkU2dUFH3/5hg7tgdE+xPYH/6tGozmE1luK+LRIJ/bd9uW7sSGXQq8bGB +u/M9LmeRAoGBANz+Rvhzl5cjjvUpjLcsu2tGjWK5uAvbcHTQd8bjHQINtKTQREFo +CoEA8DD6qiyhgwuH7BIVNjjOr9LcMjuorgbcVTOBVM5RLG7KUUPCDDejlmLI+s9w +8VKv6Vx8bt8901IUsIhNJtfoZOXEBbJVDsU3iD5zFrA9dj2EQNWG2D8lAoGBANv/ +T4NMs174oyrZYRTHOoepvlvX9zm9Ptcz9P7GcdZbg3jkQSXKAZqUwczDT8FzdxXH +ycwaCsVpikEPGJaKCnhSV1Kh2tR8H05kOxPy3vzpzsFtfrFyTPFzydOES//A+ZHD +ydcNph1K8Ztx+1YzTOj9KjOyuRpb5kUvO2c4zp31AoGBAK9HTusIY5eQsHZq+hze +8dfoIYPIYd2lstAz+Ixa3kseq8R9G2X1Kz+eiuOOLSMxB0tCB09gW5068eGAnKcM +5tqyLzGmxqjNYTyOY14mrqICseiwF54oqn823xRn7VhLJSzZFBtHdiORQ1Wp4ArN +w+VQYlOF3Nz0IrAwEWxKg4GxAoGBANlBOHShukF/qSMXqRer59ExgBuTG0KZ8QT0 ++mzf7GuT1DH+t5dp9kuBvCFKf+i67k9EDbTRwvFRWIcHMXD4wX4xUqr3y/Mq4H+5 +293Haw64lsXOK99w0Stg/V80txjKqauZfioyAGnNKOwpk9t8reconBSR2tp9Btor +2q4FG4ZBAoGANLnC8I3eHWSj3/ME+tOCu7EiiDtiE2Kt1UEtreQGyeF/cPBsDiSF +3XxgYO9bCh0TGX52e4Bfffra5f1Hvgw84dESQWunYJByb19a737MI8RN+RHKjaSo +rBjbUpA16Fi8NSro/mXDLCh8mTzu0tPG+e1jqcEVc5JDLYIau12j6jw= +-----END RSA PRIVATE KEY----- +ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg0NeCnpO5ZAzWmMX6XwjrFyDi+JRrPLNb0vrEYqJp+bEAAAADAQABAAABAQC96eyjDJkj80k2JJ2imXQTXb4VfjEXHxPClX4uw0Th7dJ6NxvKb+AfAbaFdYu3xJjyUhFkHg0hPtMhe/ubq0wrejkTtYwd87iWj8wu+aiSziRexphXClxNt8RWv+1mgAVZuBSPHg4jykrYzpaQOqmIiOcMBpumFpwA2cXNRgLbEdZ4uwpNjBYwxGigh1m50OiFvcXFvrwvGkkqDwExIaCqSoK+E3NmTLt6I5eTVvjdhxSzKqwF65vY9XWqh4w1JP2NHCQSkyh2rlC4WM0mpkyL4ZmJdIsRFj1DxN7Ovma6HS8AKJeiDyShuWounwCsoK33onjr+ib9cYUAvsKTdB9pAAAAAAAAAAAAAAABAAAACmVrb250c2V2b3kAAAAOAAAACmVrb250c2V2b3kAAAAAAAAAAAAAAABZPPDKAAAAAAAAAHYAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOdGVsZXBvcnQtcm9sZXMAAAAwAAAALHsidmVyc2lvbiI6InYxIiwicm9sZXMiOlsidXNlcjpla29udHNldm95Il19AAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAMA/0pVkfFhDPUDosZpM9nP/r/t6tORkmhXCxMkLZiS7+kg0htYSDLmwFGfzgSbYAH6Bryu9BZOxv1W23WW9oW7IdJpwfCpuyzFoRN0/2mHhAAHETtxucksTgYNwN+dDXF/IzG/QGVYswP4ENte4ZuNsd3bquBu+opK7CXU4B+UtsY0JUcV7gU5TzCZBdFpzLgB2VUpiHlFg0PUuV74aZmwzHlwoONBSIn2FZpHmvN2ZUdqTHSjof1vgH20cScMWGk05dFuM5gWjHEYC1gwdPpmTGcgN93SAQwKiAUQ6ZnJ+lVhzSp+/vxVz/aecDnrza+xI26DnB/nEEiCMu92WYxEAAAEPAAAAB3NzaC1yc2EAAAEAQRUml83QKsEeWB0WswfR1rvEzzumYRn/CAMTSGsF99bNzHmZ0lJbwCzNdl0hlJ3tGVPhANL5WwWuiLN1q6O8qrUU4cGJK3L8eNFUXmJIVc1xH2bIaws+nHikqPHnxbtAzJBbHeCngBX7eVT69bW6AdgWSHSzlPRaAaApMoEwVIMKOLiedjy7D9s/Cd+GtOtxTMoG/LmFBnvUiuXWwiQ658MRrg65ATl0x24ErJqz2cnj52Sy5G6SNUrENRqkP8TxRtp6a+FT1oJQ/2LqLwnlPQpb41j6fDqLRa47NU2TRnYRv0rhCMHO5tIhA51qhRlU9R2m5BH5o1JswN/phMgbjg== diff --git a/fixtures/certs/identities/key-cert.pub b/fixtures/certs/identities/key-cert.pub new file mode 100644 index 0000000000000..a5914216e5a28 --- /dev/null +++ b/fixtures/certs/identities/key-cert.pub @@ -0,0 +1 @@ +ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgdLgAzmQvFSeuCGB+S0rIV0MTdPq7bwJUS9JF6pBKy9YAAAADAQABAAABAQDYImS0r2MciuEMRrA+v6i6BdvpiqgEfq7+K5hxLgKx6e8DQ2jGV8raDQmyd/NCaastrEBc4V6MN9MzONwLjUALV6/X9w5g7/11rPcfHCS2ak2fcU2SsX5cIS8InBcGrLif5mhbe9LmlRv/0LKIYAO/1AJCtm6hSO8pfaR4jdNKnPuAj6ZeP8M3n+9kPH1bhx8NeUOvJkEwcr8lP/cSkx4JUY6vRbs2o4y98lm38b8CLgxIYFlFJybBhgPD/a2MpoJYLg9HGmbNXKuuHeT4srWRi0IW1JFrGbKs5h5Q6T57DYzX2XpuUPHjZfHnLYzVrw1DYc4/R0JUpwZIndD8HybHAAAAAAAAAAAAAAABAAAACmVrb250c2V2b3kAAAAOAAAACmVrb250c2V2b3kAAAAAAAAAAAAAAABZPkg1AAAAAAAAAHYAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOdGVsZXBvcnQtcm9sZXMAAAAwAAAALHsidmVyc2lvbiI6InYxIiwicm9sZXMiOlsidXNlcjpla29udHNldm95Il19AAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAMA/0pVkfFhDPUDosZpM9nP/r/t6tORkmhXCxMkLZiS7+kg0htYSDLmwFGfzgSbYAH6Bryu9BZOxv1W23WW9oW7IdJpwfCpuyzFoRN0/2mHhAAHETtxucksTgYNwN+dDXF/IzG/QGVYswP4ENte4ZuNsd3bquBu+opK7CXU4B+UtsY0JUcV7gU5TzCZBdFpzLgB2VUpiHlFg0PUuV74aZmwzHlwoONBSIn2FZpHmvN2ZUdqTHSjof1vgH20cScMWGk05dFuM5gWjHEYC1gwdPpmTGcgN93SAQwKiAUQ6ZnJ+lVhzSp+/vxVz/aecDnrza+xI26DnB/nEEiCMu92WYxEAAAEPAAAAB3NzaC1yc2EAAAEAmfMaUJlv4LQeOl9908gsYF2QuJxNTGVp9x2SWHkVEhZ05fOrbdWyIc/h3c2xWczwhFPRbJBMziFD2hsnrCxVYbcwM9jSvozwLaEKaoDmuok/ueeFokz5GuOQNV3TGbx58ZtOoH89ebuyl/LQR6f9eR81iZvaZ7URSRWSZRhrYZ9XrNtnvb4ggbP/RJZwi45bvUZ3ybebK+mgoeRoqRVdkp54Wzy/dY0mTHt6MuvBrm3J4FnrLqNNJY+68C1PZTi7ZveAr2NJqJf2QHil1Ar8uuDY7vRK2aU8UMKBgPCdzTm8wGn/9Jy+KnkbXBs+W2V63MmtwA3GYfjrMcWC72nnLg== diff --git a/fixtures/certs/identities/lonekey b/fixtures/certs/identities/lonekey new file mode 100644 index 0000000000000..8ce739f317ce0 --- /dev/null +++ b/fixtures/certs/identities/lonekey @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA2CJktK9jHIrhDEawPr+ougXb6YqoBH6u/iuYcS4CsenvA0No +xlfK2g0JsnfzQmmrLaxAXOFejDfTMzjcC41AC1ev1/cOYO/9daz3HxwktmpNn3FN +krF+XCEvCJwXBqy4n+ZoW3vS5pUb/9CyiGADv9QCQrZuoUjvKX2keI3TSpz7gI+m +Xj/DN5/vZDx9W4cfDXlDryZBMHK/JT/3EpMeCVGOr0W7NqOMvfJZt/G/Ai4MSGBZ +RScmwYYDw/2tjKaCWC4PRxpmzVyrrh3k+LK1kYtCFtSRaxmyrOYeUOk+ew2M19l6 +blDx42Xx5y2M1a8NQ2HOP0dCVKcGSJ3Q/B8mxwIDAQABAoIBAQCzQdtKbIiEPL8o +Ylx8vpMfLgrVqLVvfO6ASgIWJLYBf2dHypnUny3UKaoYRhoQw/lAWTPMlPLI0ugs +/ISsZAtxHNnwAa0AQytxdPJE8B+W15Xnnp5bAzEtEiyjNGp4k7pQjFWTQqCJs7PU +OPBJ4XBaomj5kbsxs38AZ3+IqQBMSV2HW0HB01xrO3G5s1i5SnNbU+Vuyl4vo6gm +wUhrdpPGr6lsaeO3bODCO+rcDfam3HYQmbEKnUa34gpmNXoJLeLJmSdlyhv2jsJX +YFlZvegFE1BCoaU5nvwHa0MhitnL4sN0iCZgIzAruFPJ9Bvl0oJ973srpf99gKM7 +oq4him4hAoGBANmxrYqwHWwiOZfEk93FcmFxg6lIrgpCIZ8hvjnF3gAa3NGOQepg +i575m70RY4yU3C35+WbaEBAXl9GLcvaK8enoIXchiWqYk9JtltyZXJlPwY76dJZt +T4/icwHFyvgDqd/xEs6eh36SApcgWfPFddgDequV6DvDQOhsPpFmBwARAoGBAP4q +dNrYUuMtiIff6fItuvgf4kB0U6JDFFsTvBG5Po1E9pFplh6lIyHpv+c9T8P6lZho +fGzbY35i8Ov7lqcrVxtEsiqmPWR6QBnHtEuChtC4vb6hl2kV/MlYc+tC4APd23xg +DzYqLdVOJ6RB3rfodPbVYvu01+pkR1NKvwI57RFXAoGBAIHWp1sAj4vfHdqXNFoh +WYck3RIqdyNHLiZrSbnLeg01+F5EKqxPyPaYiXrK1EUUw/3oCgh+JvZyG+qu8XJ6 +jK6l9M/JANzDA+eN1VzdW41VNGzClKbjq4B134I/Jj+mb7tRXZY+lzG2hDT+5qeu +LgsYiCGu10RNwHjflrHB2IsBAoGAXLZb/eBfC+N2JCo3ilHIG+51d5F3WH8jk711 +IvnxqVJ4pr5fNjqCwEIl8FHbIN/tZbTnfkXg2x94Rnx1jfEvSxEZ9JjDWD2H2F+S +kuDAEK7y9/C17G1K0p9jWXQBhyeMgqf/pIBqS57AsxgcB/XRhKB+BNcI08VUhzuC +xWsf4O0CgYEA0jdKH7Nr+G0kHGb79p3/fEM0H43sdyLXmudXXrym0wDbLNKBY6ny +k+kA63rxsMCKTPIAHcj77Mjw2MnjEwsBKZtNhewr1fv5GgqXI3G7WLKe6W9RFhbj +RMndSXGdA3J8ZJ18CF/pbGTGAhGUx2KQvHIFcXoW23fjqnHMy2mqWWI= +-----END RSA PRIVATE KEY----- diff --git a/lib/client/api.go b/lib/client/api.go index 6f9dda8faef6b..349a5281a1718 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -914,6 +914,8 @@ func (tc *TeleportClient) authMethods() []ssh.AuthMethod { // ConnectToProxy dials the proxy server and returns ProxyClient if successful func (tc *TeleportClient) ConnectToProxy() (*ProxyClient, error) { + var err error + proxyPrincipal := tc.getProxySSHPrincipal() proxyAddr := tc.Config.ProxySSHHostPort() sshConfig := &ssh.ClientConfig{ @@ -939,9 +941,10 @@ func (tc *TeleportClient) ConnectToProxy() (*ProxyClient, error) { // try to authenticate using every non interactive auth method we have: for i, m := range tc.authMethods() { log.Infof("[CLIENT] connecting proxy=%v login='%v' method=%d", proxyAddr, sshConfig.User, i) + var sshClient *ssh.Client sshConfig.Auth = []ssh.AuthMethod{m} - sshClient, err := ssh.Dial("tcp", proxyAddr, sshConfig) + sshClient, err = ssh.Dial("tcp", proxyAddr, sshConfig) if err != nil { if utils.IsHandshakeFailedError(err) { log.Warn(err) @@ -953,9 +956,12 @@ func (tc *TeleportClient) ConnectToProxy() (*ProxyClient, error) { return makeProxyClient(sshClient, m), nil } // we have exhausted all auth existing auth methods and local login - // is disabled in configuration - if tc.Config.SkipLocalAuth { - return nil, trace.BadParameter("failed to authenticate with proxy %v", proxyAddr) + // is disabled in configuration, or the user refused connecting to untrusted hosts + if tc.Config.SkipLocalAuth || tc.localAgent.UserRefusedHosts() { + if err == nil { + err = trace.BadParameter("failed to authenticate with proxy %v", proxyAddr) + } + return nil, trace.Wrap(err) } // if we get here, it means we failed to authenticate using stored keys // and we need to ask for the login information @@ -968,7 +974,6 @@ func (tc *TeleportClient) ConnectToProxy() (*ProxyClient, error) { } return nil, trace.Wrap(err) } - // After successfull login we have local agent updated with latest // and greatest auth information, try it now sshConfig.Auth = []ssh.AuthMethod{authMethod} @@ -1041,7 +1046,7 @@ func (tc *TeleportClient) Login() (*CertAuthMethod, error) { // extract the new certificate out of the response key.Cert = response.Cert - // save the list of CAs we trust to the cache file + // save the list of CAs we trust to ~/.tsh/known_hosts err = tc.localAgent.AddHostSignersToCache(response.HostSigners) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/client/keyagent.go b/lib/client/keyagent.go index 5e88ad82263eb..8ab105841e0b3 100644 --- a/lib/client/keyagent.go +++ b/lib/client/keyagent.go @@ -36,6 +36,13 @@ type LocalKeyAgent struct { agent.Agent // Agent is the teleport agent keyStore LocalKeyStore // keyStore is the storage backend for certificates and keys sshAgent agent.Agent // sshAgent is the system ssh agent + + // map of "no hosts". these are hosts that user manually (via keyboard + // input) refused connecting to. + noHosts map[string]bool + + // function which asks a user to trust host/key combination (during host auth) + hostPromptFunc func(host string, k ssh.PublicKey) error } // NewLocalAgent reads all Teleport certificates from disk (using FSLocalKeyStore), @@ -50,6 +57,7 @@ func NewLocalAgent(keyDir, username string) (a *LocalKeyAgent, err error) { Agent: agent.NewKeyring(), keyStore: keystore, sshAgent: connectToSSHAgent(), + noHosts: make(map[string]bool), } // unload all teleport keys from the agent first to ensure @@ -199,38 +207,57 @@ func (a *LocalKeyAgent) AddHostSignersToCache(hostSigners []services.CertAuthori return nil } +// UserRefusedHosts returns 'true' if a user refuses connecting to remote hosts +// when prompted during host authorization +func (a *LocalKeyAgent) UserRefusedHosts() bool { + return len(a.noHosts) > 0 +} + // CheckHostSignature checks if the given host key was signed by one of the trusted // certificaate authorities (CAs) -func (a *LocalKeyAgent) CheckHostSignature(hostId string, remote net.Addr, key ssh.PublicKey) error { +func (a *LocalKeyAgent) CheckHostSignature(host string, remote net.Addr, key ssh.PublicKey) error { + hostPromptFunc := func(host string, key ssh.PublicKey) error { + userAnswer := "no" + if !a.noHosts[host] { + fmt.Printf("The authenticity of host '%s' can't be established. "+ + "Its public key is:\n%s\nAre you sure you want to continue (yes/no)? ", + host, ssh.MarshalAuthorizedKey(key)) + + bytes := make([]byte, 12) + os.Stdin.Read(bytes) + userAnswer = strings.TrimSpace(strings.ToLower(string(bytes))) + } + if !strings.HasPrefix(userAnswer, "y") { + return trace.AccessDenied("untrusted host %v", host) + } + // success + return nil + } + // overwritten host prompt func? (probably for tests) + if a.hostPromptFunc != nil { + hostPromptFunc = a.hostPromptFunc + } cert, ok := key.(*ssh.Certificate) if !ok { // not a signed cert? perhaps we're given a host public key (happens when the host is running // sshd instead of teleport daemon - keys, _ := a.keyStore.GetKnownHostKeys(hostId) + keys, _ := a.keyStore.GetKnownHostKeys(host) if len(keys) > 0 && sshutils.KeysEqual(key, keys[0]) { - log.Debugf("[KEY AGENT] verified host %s", hostId) + log.Debugf("[KEY AGENT] verified host %s", host) return nil } - // ask the user if they want to trust this host - fmt.Printf("The authenticity of host '%s' can't be established. "+ - "Its public key is:\n%s\nAre you sure you want to continue (yes/no)? ", - hostId, ssh.MarshalAuthorizedKey(key)) - - bytes := make([]byte, 12) - os.Stdin.Read(bytes) - if strings.TrimSpace(strings.ToLower(string(bytes)))[0] != 'y' { - err := trace.AccessDenied("untrusted host %v", hostId) - log.Error(err) - return err + // ask user: + if err := hostPromptFunc(host, key); err != nil { + a.noHosts[host] = true + return trace.Wrap(err) } // remember the host key (put it into 'known_hosts') - if err := a.keyStore.AddKnownHostKeys(hostId, []ssh.PublicKey{key}); err != nil { + if err := a.keyStore.AddKnownHostKeys(host, []ssh.PublicKey{key}); err != nil { log.Warnf("error saving the host key: %v", err) } - // success return nil } - + key = cert.SignatureKey // we are given a certificate. see if it was signed by any of the known_host keys: keys, err := a.keyStore.GetKnownHostKeys("") if err != nil { @@ -240,12 +267,22 @@ func (a *LocalKeyAgent) CheckHostSignature(hostId string, remote net.Addr, key s log.Debugf("[KEY AGENT] got %d known hosts", len(keys)) for i := range keys { if sshutils.KeysEqual(cert.SignatureKey, keys[i]) { - log.Debugf("[KEY AGENT] verified host %s", hostId) + log.Debugf("[KEY AGENT] verified host %s", host) return nil } } - err = trace.AccessDenied("untrusted host %v", hostId) - log.Error(err) + // final step: if we have not seen the host key/cert before, lets ask the user if + // he trusts it, and add to the known_hosts if he says "yes" + if err = hostPromptFunc(host, key); err != nil { + // he said "no" + a.noHosts[host] = true + return trace.Wrap(err) + } + // user said "yes" + err = a.keyStore.AddKnownHostKeys(host, []ssh.PublicKey{key}) + if err != nil { + log.Warn(err) + } return err } @@ -277,7 +314,7 @@ func (a *LocalKeyAgent) AddKey(host string, username string, key *Key) (*CertAut return nil, trace.Wrap(err) } - return methodForCert(signer), nil + return NewAuthMethodForCert(signer), nil } // DeleteKey removes the key from the key store as well as unloading the key from the agent. @@ -303,19 +340,23 @@ func (a *LocalKeyAgent) DeleteKey(proxyHost string, username string) error { // 2. Itself (disk-based local agent) func (a *LocalKeyAgent) AuthMethods() (m []ssh.AuthMethod) { // combine our certificates with external SSH agent's: - var certs []ssh.Signer + var signers []ssh.Signer if ourCerts, _ := a.Signers(); ourCerts != nil { - certs = append(certs, ourCerts...) + signers = append(signers, ourCerts...) } if a.sshAgent != nil { if sshAgentCerts, _ := a.sshAgent.Signers(); sshAgentCerts != nil { - certs = append(certs, sshAgentCerts...) + signers = append(signers, sshAgentCerts...) } } // for every certificate create a new "auth method" and return them - m = make([]ssh.AuthMethod, len(certs)) - for i := range certs { - m[i] = methodForCert(certs[i]) + m = make([]ssh.AuthMethod, 0) + for i := range signers { + // filter out non-certificates (like regular public SSH keys stored in the SSH agent): + _, ok := signers[i].PublicKey().(*ssh.Certificate) + if ok { + m = append(m, NewAuthMethodForCert(signers[i])) + } } return m } @@ -331,7 +372,7 @@ type CertAuthMethod struct { Cert ssh.Signer } -func methodForCert(cert ssh.Signer) *CertAuthMethod { +func NewAuthMethodForCert(cert ssh.Signer) *CertAuthMethod { return &CertAuthMethod{ Cert: cert, AuthMethod: ssh.PublicKeysCallback(func() ([]ssh.Signer, error) { diff --git a/lib/client/keyagent_test.go b/lib/client/keyagent_test.go index 15d1fa15b9cb1..3c5a67f3e2a61 100644 --- a/lib/client/keyagent_test.go +++ b/lib/client/keyagent_test.go @@ -28,7 +28,7 @@ import ( "golang.org/x/crypto/ssh/agent" "github.com/gravitational/teleport" - "github.com/gravitational/teleport/lib/auth/native" + "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/trace" @@ -202,8 +202,61 @@ func (s *KeyAgentTestSuite) TestLoadKey(c *check.C) { c.Assert(err, check.IsNil) } +func (s *KeyAgentTestSuite) TestHostVerification(c *check.C) { + // make a new local agent + lka, err := NewLocalAgent(s.keyDir, s.username) + c.Assert(err, check.IsNil) + + // by default user has not refused any hosts: + c.Assert(lka.UserRefusedHosts(), check.Equals, false) + + // make a fake host key: + keygen := testauthority.New() + _, pub, err := keygen.GenerateKeyPair("") + c.Assert(err, check.IsNil) + pk, _, _, _, err := ssh.ParseAuthorizedKey(pub) + c.Assert(err, check.IsNil) + + // test user refusing connection: + fakeErr := trace.Errorf("luna cannot be trusted!") + lka.hostPromptFunc = func(host string, k ssh.PublicKey) error { + c.Assert(host, check.Equals, "luna") + c.Assert(k, check.Equals, pk) + return fakeErr + } + var a net.TCPAddr + err = lka.CheckHostSignature("luna", &a, pk) + c.Assert(err, check.NotNil) + c.Assert(err.Error(), check.Equals, "luna cannot be trusted!") + c.Assert(lka.UserRefusedHosts(), check.Equals, true) + + // clean user answer: + delete(lka.noHosts, "luna") + c.Assert(lka.UserRefusedHosts(), check.Equals, false) + + // now lets simulate user being asked: + userWasAsked := false + lka.hostPromptFunc = func(host string, k ssh.PublicKey) error { + // user answered "yes" + userWasAsked = true + return nil + } + c.Assert(lka.UserRefusedHosts(), check.Equals, false) + err = lka.CheckHostSignature("luna", &a, pk) + c.Assert(err, check.IsNil) + c.Assert(userWasAsked, check.Equals, true) + + // now lets simulate automatic host verification (no need to ask user, he + // just said "yes") + userWasAsked = false + c.Assert(lka.UserRefusedHosts(), check.Equals, false) + err = lka.CheckHostSignature("luna", &a, pk) + c.Assert(err, check.IsNil) + c.Assert(userWasAsked, check.Equals, false) +} + func makeKey(username string, allowedLogins []string, ttl time.Duration) (*Key, error) { - keygen := native.New() + keygen := testauthority.New() privateKey, publicKey, err := keygen.GenerateKeyPair("") if err != nil { diff --git a/lib/srv/sess.go b/lib/srv/sess.go index 78672e76c7813..635ae53e6fe7b 100644 --- a/lib/srv/sess.go +++ b/lib/srv/sess.go @@ -531,6 +531,7 @@ func (s *session) start(ch ssh.Channel, ctx *ctx) error { if err != nil { return trace.Wrap(err) } + ctx.Debugf("session exec: %#v", cmd) if err := s.term.run(cmd); err != nil { ctx.Errorf("shell command failed: %v", err) return trace.ConvertSystemError(err) diff --git a/lib/sshutils/signer.go b/lib/sshutils/signer.go index e5391ddefc994..e5bb3046f41c0 100644 --- a/lib/sshutils/signer.go +++ b/lib/sshutils/signer.go @@ -22,8 +22,9 @@ import ( "golang.org/x/crypto/ssh" ) -// NewSigner returns new ssh Signer using OpenSSH certificates -// to authenticate itself +// NewSigner returns new ssh Signer from private key + certificate pair. The +// signer can be used to create "auth methods" i.e. login into Teleport SSH +// servers. func NewSigner(keyBytes, certBytes []byte) (ssh.Signer, error) { keySigner, err := ssh.ParsePrivateKey(keyBytes) if err != nil { diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go index 93797bea67a7f..ac7f4fae3a817 100644 --- a/tool/tctl/common/tctl.go +++ b/tool/tctl/common/tctl.go @@ -89,10 +89,17 @@ type AuthCommand struct { genTTL time.Duration exportAuthorityFingerprint string exportPrivateKeys bool - outDir string + output string + outputFormat string compatVersion string } +const ( + IdentityFormatFile = "file" + IdentityFormatDir = "dir" + DefaultIdentityFormat = IdentityFormatFile +) + type SAMLCommand struct { config *service.Config name string @@ -224,21 +231,23 @@ func Run() { samlExport.Flag("name", "name of the connector to export").StringVar(&cmdSAML.name) // operations with authorities - auth := app.Command("auth", "Operations with user and host certificate authorities").Hidden() - auth.Flag("type", "authority type, 'user' or 'host'").StringVar(&cmdAuth.authType) + auth := app.Command("auth", "Operations with user and host certificate authorities (CAs)").Hidden() authList := auth.Command("ls", "List trusted certificate authorities (CAs)") - authExport := auth.Command("export", "Export CA keys to standard output") + authList.Flag("type", "certificate type: 'user' or 'host'").StringVar(&cmdAuth.authType) + authExport := auth.Command("export", "Export public cluster (CA) keys to stdout") authExport.Flag("keys", "if set, will print private keys").BoolVar(&cmdAuth.exportPrivateKeys) authExport.Flag("fingerprint", "filter authority by fingerprint").StringVar(&cmdAuth.exportAuthorityFingerprint) authExport.Flag("compat", "export cerfiticates compatible with specific version of Teleport").StringVar(&cmdAuth.compatVersion) + authExport.Flag("type", "certificate type: 'user' or 'host'").StringVar(&cmdAuth.authType) authGenerate := auth.Command("gen", "Generate a new SSH keypair") authGenerate.Flag("pub-key", "path to the public key").Required().StringVar(&cmdAuth.genPubPath) authGenerate.Flag("priv-key", "path to the private key").Required().StringVar(&cmdAuth.genPrivPath) - authSign := auth.Command("sign", "Create a signed user session cerfiticate") + authSign := auth.Command("sign", "Create an identity file(s) for a given user") authSign.Flag("user", "Teleport user name").Required().StringVar(&cmdAuth.genUser) - authSign.Flag("out", "Output directory [defaults to current]").Short('o').StringVar(&cmdAuth.outDir) + authSign.Flag("out", "identity output").Short('o').StringVar(&cmdAuth.output) + authSign.Flag("format", "identity format: 'file' (default) or 'dir'").Default(DefaultIdentityFormat).StringVar(&cmdAuth.outputFormat) authSign.Flag("ttl", "TTL (time to live) for the generated certificate").Default(fmt.Sprintf("%v", defaults.CertDuration)).DurationVar(&cmdAuth.genTTL) // operations with reverse tunnels @@ -322,14 +331,9 @@ func Run() { err = cmdTokens.Del(client) case authSign.FullCommand(): err = cmdAuth.GenerateAndSignKeys(client) - if err != nil { - utils.FatalError(err) - } - return } - if err != nil { - logrus.Error(trace.DebugReport(err)) + logrus.Error(err) utils.FatalError(err) } } @@ -754,39 +758,64 @@ func (a *AuthCommand) GenerateAndSignKeys(client *auth.TunClient) error { return trace.Wrap(err) } - certPath := a.genUser + "-cert.pub" - keyPath := a.genUser - pubPath := a.genUser + ".pub" - - // --out flag - if a.outDir != "" { - if !utils.IsDir(a.outDir) { - if err = os.MkdirAll(a.outDir, 0770); err != nil { + switch a.outputFormat { + // + // dump user identity into a single file: + // + case IdentityFormatFile: + var ( + output io.Writer = os.Stdout + beQuiet bool = true + ) + if a.output != "" { + beQuiet = false + f, err := os.OpenFile(a.output, os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { return trace.Wrap(err) } + output = f + defer f.Close() + } + // write key: + if _, err = output.Write(privateKey); err != nil { + return trace.Wrap(err) + } + // append cert: + if _, err = output.Write(cert); err != nil { + return trace.Wrap(err) + } + if !beQuiet { + fmt.Printf("Identity file: %s\n", a.output) + } + // + // dump user identity into separate files: + // + case IdentityFormatDir: + certPath := a.genUser + "-cert.pub" + keyPath := a.genUser + + // --out flag + if a.output != "" { + if !utils.IsDir(a.output) { + if err = os.MkdirAll(a.output, 0770); err != nil { + return trace.Wrap(err) + } + } + certPath = filepath.Join(a.output, certPath) + keyPath = filepath.Join(a.output, keyPath) } - certPath = filepath.Join(a.outDir, certPath) - keyPath = filepath.Join(a.outDir, keyPath) - pubPath = filepath.Join(a.outDir, pubPath) - } - - err = ioutil.WriteFile(certPath, cert, 0600) - if err != nil { - return trace.Wrap(err) - } - err = ioutil.WriteFile(keyPath, privateKey, 0600) - if err != nil { - return trace.Wrap(err) - } + err = ioutil.WriteFile(certPath, cert, 0600) + if err != nil { + return trace.Wrap(err) + } - err = ioutil.WriteFile(pubPath, publicKey, 0600) - if err != nil { - return trace.Wrap(err) + err = ioutil.WriteFile(keyPath, privateKey, 0600) + if err != nil { + return trace.Wrap(err) + } + fmt.Printf("Private key: %v\nCertificate: %v\n", keyPath, certPath) } - - fmt.Printf("Public key : %v\nPrivate key: %v\nCertificate: %v\n", - pubPath, keyPath, certPath) return nil } @@ -993,7 +1022,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.UpsertSAMLConnector(conn); err != nil { return trace.Wrap(err) } - fmt.Printf("SAML connector %v upserted\n", conn.GetName()) + fmt.Printf("created SAML connector: %v\n", conn.GetName()) case services.KindOIDCConnector: conn, err := services.GetOIDCConnectorMarshaler().UnmarshalOIDCConnector(raw.Raw) if err != nil { @@ -1002,7 +1031,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.UpsertOIDCConnector(conn); err != nil { return trace.Wrap(err) } - fmt.Printf("OIDC connector %v upserted\n", conn.GetName()) + fmt.Printf("created OIDC connector: %v\n", conn.GetName()) case services.KindReverseTunnel: tun, err := services.GetReverseTunnelMarshaler().UnmarshalReverseTunnel(raw.Raw) if err != nil { @@ -1011,7 +1040,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.UpsertReverseTunnel(tun); err != nil { return trace.Wrap(err) } - fmt.Printf("reverse tunnel %v upserted\n", tun.GetName()) + fmt.Printf("created reverse tunnel: %v\n", tun.GetName()) case services.KindCertAuthority: ca, err := services.GetCertAuthorityMarshaler().UnmarshalCertAuthority(raw.Raw) if err != nil { @@ -1020,7 +1049,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.UpsertCertAuthority(ca); err != nil { return trace.Wrap(err) } - fmt.Printf("cert authority %v upserted\n", ca.GetName()) + fmt.Printf("created cert authority: %v \n", ca.GetName()) case services.KindUser: user, err := services.GetUserMarshaler().UnmarshalUser(raw.Raw) if err != nil { @@ -1029,7 +1058,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.UpsertUser(user); err != nil { return trace.Wrap(err) } - fmt.Printf("user %v upserted\n", user.GetName()) + fmt.Printf("created user: %v\n", user.GetName()) case services.KindRole: role, err := services.GetRoleMarshaler().UnmarshalRole(raw.Raw) if err != nil { @@ -1042,7 +1071,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.UpsertRole(role, backend.Forever); err != nil { return trace.Wrap(err) } - fmt.Printf("role %v upserted\n", role.GetName()) + fmt.Printf("created role: %v\n", role.GetName()) case services.KindNamespace: ns, err := services.UnmarshalNamespace(raw.Raw) if err != nil { @@ -1051,7 +1080,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.UpsertNamespace(*ns); err != nil { return trace.Wrap(err) } - fmt.Printf("namespace %v upserted\n", ns.Metadata.Name) + fmt.Printf("created namespace: %v\n", ns.Metadata.Name) case services.KindTrustedCluster: tc, err := services.GetTrustedClusterMarshaler().Unmarshal(raw.Raw) if err != nil { @@ -1060,7 +1089,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.UpsertTrustedCluster(tc); err != nil { return trace.Wrap(err) } - fmt.Printf("trusted cluster %q upserted\n", tc.GetName()) + fmt.Printf("created trusted cluster: %q\n", tc.GetName()) case services.KindClusterAuthPreference: cap, err := services.GetAuthPreferenceMarshaler().Unmarshal(raw.Raw) if err != nil { @@ -1069,7 +1098,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.SetClusterAuthPreference(cap); err != nil { return trace.Wrap(err) } - fmt.Printf("cluster auth preference upserted\n") + fmt.Printf("updated cluster auth preferences\n") case services.KindUniversalSecondFactor: universalSecondFactor, err := services.GetUniversalSecondFactorMarshaler().Unmarshal(raw.Raw) if err != nil { @@ -1078,7 +1107,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error { if err := client.SetUniversalSecondFactor(universalSecondFactor); err != nil { return trace.Wrap(err) } - fmt.Printf("universal second factor upserted\n") + fmt.Printf("updated utf settings\n") case "": return trace.BadParameter("missing resource kind") default: diff --git a/tool/tsh/common/common_test.go b/tool/tsh/common/common_test.go index 563afe81c2923..b5f8d64fafbff 100644 --- a/tool/tsh/common/common_test.go +++ b/tool/tsh/common/common_test.go @@ -17,6 +17,9 @@ limitations under the License. package common import ( + "fmt" + "io/ioutil" + "net" "os" "testing" "time" @@ -26,6 +29,8 @@ import ( "github.com/gravitational/teleport/lib/utils" "gopkg.in/check.v1" + + "golang.org/x/crypto/ssh" ) // bootstrap check @@ -82,3 +87,42 @@ func (s *MainTestSuite) TestMakeClient(c *check.C) { }, }) } + +func (s *MainTestSuite) TestIdentityRead(c *check.C) { + // 3 different types of identities + ids := []string{ + "cert-key.pem", // cert + key concatenated togther, cert first + "key-cert.pem", // cert + key concatenated togther, key first + "key", // two separate files: key and key-cert.pub + } + for _, id := range ids { + // test reading: + k, cb, err := loadIdentity(fmt.Sprintf("../../../fixtures/certs/identities/%s", id)) + c.Assert(err, check.IsNil) + c.Assert(k, check.NotNil) + c.Assert(cb, check.IsNil) + + // test creating an auth method from the key: + am, err := authFromIdentity(k) + c.Assert(err, check.IsNil) + c.Assert(am, check.NotNil) + } + k, _, err := loadIdentity("../../../fixtures/certs/identities/lonekey") + c.Assert(k, check.IsNil) + c.Assert(err, check.NotNil) + + // lets read an indentity which includes a CA cert + k, hostAuthCallback, err := loadIdentity("../../../fixtures/certs/identities/key-cert-ca.pem") + c.Assert(err, check.IsNil) + c.Assert(k, check.NotNil) + c.Assert(hostAuthCallback, check.NotNil) + // prepare the cluster CA separately + certBytes, err := ioutil.ReadFile("../../../fixtures/certs/identities/ca.pem") + c.Assert(err, check.IsNil) + _, hosts, cert, _, _, err := ssh.ParseKnownHosts(certBytes) + c.Assert(err, check.IsNil) + var a net.Addr + // host auth callback must succeed + err = hostAuthCallback(hosts[0], a, cert) + c.Assert(err, check.IsNil) +} diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 44420ac9e1adc..f7222ac1e3a23 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -17,8 +17,12 @@ limitations under the License. package common import ( + "bufio" + "bytes" "context" "fmt" + "io/ioutil" + "net" "os" "os/signal" "path/filepath" @@ -26,10 +30,13 @@ import ( "syscall" "time" + "golang.org/x/crypto/ssh" + "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/session" + "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/teleagent" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/trace" @@ -100,13 +107,13 @@ type CLIConf struct { Gops bool // GopsAddr specifies to gops addr to listen on GopsAddr string + // IdentityFile is an argument to -i flag (path to the private key+cert file) + IdentityFile string } // Run executes TSH client. same as main() but easier to test func Run(args []string, underTest bool) { - var ( - cf CLIConf - ) + var cf CLIConf cf.IsUnderTest = underTest utils.InitLogger(utils.LoggingForCLI, logrus.WarnLevel) @@ -114,13 +121,15 @@ func Run(args []string, underTest bool) { app := utils.InitCLIParser("tsh", "TSH: Teleport SSH client").Interspersed(false) app.Flag("login", "Remote host login").Short('l').Envar("TELEPORT_LOGIN").StringVar(&cf.NodeLogin) localUser, _ := client.Username() + app.Flag("proxy", "SSH proxy host or IP address").Envar("TELEPORT_PROXY").StringVar(&cf.Proxy) app.Flag("nocache", "do not cache cluster discovery locally").Hidden().BoolVar(&cf.NoCache) app.Flag("user", fmt.Sprintf("SSH proxy user [%s]", localUser)).Envar("TELEPORT_USER").StringVar(&cf.Username) app.Flag("cluster", "Specify the cluster to connect").Envar("TELEPORT_SITE").StringVar(&cf.SiteName) - app.Flag("proxy", "SSH proxy host or IP address").Envar("TELEPORT_PROXY").StringVar(&cf.Proxy) app.Flag("ttl", "Minutes to live for a SSH session").Int32Var(&cf.MinsToLive) + app.Flag("identity", "Identity file").Short('i').StringVar(&cf.IdentityFile) + app.Flag("insecure", "Do not verify server's certificate and host name. Use only in test environments").Default("false").BoolVar(&cf.InsecureSkipVerify) - app.Flag("namespace", "Namespace of the cluster").Default(defaults.Namespace).StringVar(&cf.Namespace) + app.Flag("namespace", "Namespace of the cluster").Default(defaults.Namespace).Hidden().StringVar(&cf.Namespace) app.Flag("gops", "Start gops endpoint on a given address").Hidden().BoolVar(&cf.Gops) app.Flag("gops-addr", "Specify gops addr to listen on").Hidden().StringVar(&cf.GopsAddr) debugMode := app.Flag("debug", "Verbose logging to stdout").Short('d').Bool() @@ -511,11 +520,40 @@ func makeClient(cf *CLIConf, useProfileLogin bool) (tc *client.TeleportClient, e // 1: start with the defaults c := client.MakeDefaultConfig() - // 2: load profile. if no --proxy is given use ~/.tsh/profile symlink otherwise - // fetch profile for exact proxy we are trying to connect to. - err = c.LoadProfile("", cf.Proxy) - if err != nil { - fmt.Printf("WARNING: Failed to load tsh profile for %q: %v\n", cf.Proxy, err) + // Look if a user identity was given via -i flag + if cf.IdentityFile != "" { + var ( + key *client.Key + identityAuth ssh.AuthMethod + expiryDate time.Time + hostAuthFunc client.HostKeyCallback + ) + // read the ID file and create an "auth method" from it: + key, hostAuthFunc, err = loadIdentity(cf.IdentityFile) + if err != nil { + return nil, trace.Wrap(err) + } + identityAuth, err = authFromIdentity(key) + if err != nil { + return nil, trace.Wrap(err) + } + c.AuthMethods = []ssh.AuthMethod{identityAuth} + if hostAuthFunc != nil { + c.HostKeyCallback = hostAuthFunc + } + + // check the expiration date + expiryDate, _ = key.CertValidBefore() + if expiryDate.Before(time.Now()) { + fmt.Fprintf(os.Stderr, "WARNING: the certificate has expired on %v\n", expiryDate) + } + } else { + // load profile. if no --proxy is given use ~/.tsh/profile symlink otherwise + // fetch profile for exact proxy we are trying to connect to. + err = c.LoadProfile("", cf.Proxy) + if err != nil { + fmt.Printf("WARNING: Failed to load tsh profile for %q: %v\n", cf.Proxy, err) + } } // 3: override with the CLI flags @@ -577,3 +615,112 @@ func refuseArgs(command string, args []string) { utils.FatalError(trace.BadParameter("%s does not expect arguments", command)) } } + +// loadIdentity loads the private key + certificate from a file +// Returns: +// - client key: user's private key+cert +// - host auth callback: function to validate the host (may be null) +// - error, if somthing happens when reading the identityf file +// +// If the "host auth callback" is not returned, user will be prompted to +// trust the proxy server. +func loadIdentity(idFn string) (*client.Key, client.HostKeyCallback, error) { + logrus.Infof("Reading identity file: ", idFn) + + f, err := os.Open(idFn) + if err != nil { + return nil, nil, trace.Wrap(err) + } + defer f.Close() + var ( + keyBuf bytes.Buffer + state int // 0: not found, 1: found beginning, 2: found ending + cert []byte + caCert []byte + ) + // read the identity file line by line: + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if state != 1 { + if strings.HasPrefix(line, "ssh") { + cert = []byte(line) + continue + } + if strings.HasPrefix(line, "@cert-authority") { + caCert = []byte(line) + continue + } + } + if state == 0 && strings.HasPrefix(line, "-----BEGIN") { + state = 1 + keyBuf.WriteString(line) + keyBuf.WriteRune('\n') + continue + } + if state == 1 { + keyBuf.WriteString(line) + if strings.HasPrefix(line, "-----END") { + state = 2 + } else { + keyBuf.WriteRune('\n') + } + } + } + // did not find the certificate in the file? look in a separate file with + // -cert.pub prefix + if len(cert) == 0 { + certFn := idFn + "-cert.pub" + logrus.Infof("certificate not found in %s. looking in %s", idFn, certFn) + cert, err = ioutil.ReadFile(certFn) + if err != nil { + return nil, nil, trace.Wrap(err) + } + } + // validate both by parsing them: + privKey, err := ssh.ParseRawPrivateKey(keyBuf.Bytes()) + if err != nil { + return nil, nil, trace.BadParameter("invalid identity: %s. %v", idFn, err) + } + signer, err := ssh.NewSignerFromKey(privKey) + if err != nil { + return nil, nil, trace.Wrap(err) + } + var hostAuthFunc client.HostKeyCallback = nil + // validate CA (cluster) cert + if len(caCert) > 0 { + _, _, pkey, _, _, err := ssh.ParseKnownHosts(caCert) + if err != nil { + return nil, nil, trace.BadParameter("CA cert parsing error: %v. cert line :%v", + err.Error(), string(caCert)) + } + // found CA cert in the indentity file? construct the host key checking function + // and return it: + hostAuthFunc = func(host string, a net.Addr, hostKey ssh.PublicKey) error { + clusterCert, ok := hostKey.(*ssh.Certificate) + if ok { + hostKey = clusterCert.SignatureKey + } + if !sshutils.KeysEqual(pkey, hostKey) { + err = trace.AccessDenied("host %v is untrusted", host) + logrus.Error(err) + return err + } + return nil + } + } + return &client.Key{ + Priv: keyBuf.Bytes(), + Pub: signer.PublicKey().Marshal(), + Cert: cert, + }, hostAuthFunc, nil +} + +// authFromIdentity returns a standard ssh.Authmethod for a given identity file +func authFromIdentity(k *client.Key) (ssh.AuthMethod, error) { + signer, err := sshutils.NewSigner(k.Priv, k.Cert) + if err != nil { + return nil, trace.Wrap(err) + } + return client.NewAuthMethodForCert(signer), nil +}