Compare commits

..

223 Commits

Author SHA1 Message Date
Manfred Touron
bae5ad94a9 Merge pull request #227 from moul/renovate/all
fix(deps): update all
2021-04-27 10:03:47 +02:00
Renovate Bot
5369a4f966 fix(deps): update all 2021-04-25 16:18:08 +00:00
Manfred Touron
1c98ef283e Merge pull request #270 from moul/dev/moul/maintenance
chore: repo maintenance 🤖
2021-04-25 18:15:51 +02:00
moul-bot
3dc2801c60 chore: repo maintenance 🤖
more details: https://github.com/moul/repoman

Signed-off-by: moul-bot <bot@moul.io>
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
2021-04-25 16:08:45 +00:00
Manfred Touron
0415f116ea Merge pull request #212 from GreyOBox/dev/GreyOBox/acls-cmd-hook 2021-04-25 11:50:28 +02:00
Manfred Touron
68ce353c5d Merge pull request #269 from moul/dev/moul/maintenance 2021-04-25 11:47:36 +02:00
Manfred Touron
f7ed3a66f2 fix: email address validator 2021-04-24 12:54:42 +00:00
moul-bot
4e9c5205c7 chore: repo maintenance 🤖
more details: https://github.com/moul/repoman

Signed-off-by: moul-bot <bot@moul.io>
2021-04-24 12:35:39 +00:00
Manfred Touron
63b4aa5533 Merge pull request #268 from moul/dependabot/github_actions/actions/cache-v2.1.5
chore(deps): bump actions/cache from v2.1.4 to v2.1.5
2021-04-21 09:09:52 +02:00
Sergey Yashchuk
d580b14d62 Merge branch 'master' into dev/GreyOBox/acls-cmd-hook 2021-04-20 18:42:36 +07:00
Sergey Yashchuk
669577de47 Merge pull request #1 from moul/master
Update from upstream master
2021-04-20 18:28:12 +07:00
dependabot[bot]
868be6af11 chore(deps): bump actions/cache from v2.1.4 to v2.1.5
Bumps [actions/cache](https://github.com/actions/cache) from v2.1.4 to v2.1.5.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.4...1a9e2138d905efd099035b49d8b7a3888c653ca8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-13 04:13:10 +00:00
Sergey Yashchuk
97bf5d3168 lint fix 2021-04-02 10:49:18 +07:00
Sergey Yashchuk
32fcfa370c Fixes related to comments in PR 2021-04-02 10:29:46 +07:00
Manfred Touron
a710e50b1e Merge pull request #239 from moul/dependabot/github_actions/actions/cache-v2.1.4
chore(deps): bump actions/cache from v2.1.3 to v2.1.4
2021-03-31 13:50:53 +02:00
dependabot[bot]
55010dcc09 chore(deps): bump actions/cache from v2.1.3 to v2.1.4
Bumps [actions/cache](https://github.com/actions/cache) from v2.1.3 to v2.1.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.3...26968a09c0ea4f3e233fdddbafd1166051a095f6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-31 10:56:17 +00:00
Manfred Touron
9413b75dc8 Merge pull request #263 from moul/dev/moul/bump-ci-go
chore: bump CI's go version
2021-03-31 12:55:17 +02:00
Manfred Touron
2648418463 chore: bump CI's go version 2021-03-31 10:52:14 +00:00
Manfred Touron
79cbaa3afe Merge pull request #262 from moul/fix/email-validator
feat: New Email validator
2021-03-28 22:46:58 +02:00
Darko Djalevski
2def328f6a fix: fix email validating in shell input
fix: test cases

fix feedback

fix: validate email with custom validator in shell input
2021-03-28 22:25:25 +02:00
Manfred Touron
ab9c53f1b0 chore: maintenance (#260) 2021-03-26 17:26:10 +01:00
Manfred Touron
614418e7be Merge pull request #243 from matteyeux/master
Fix typo in "shell commands" section in README.md
2021-03-26 16:29:29 +01:00
Manfred Touron
a5bade8761 Merge pull request #249 from jwessel/fix_host_inspect
fix: host inspect causes db errors with later operations
2021-03-26 16:28:44 +01:00
Manfred Touron
7404704bfe Merge pull request #254 from jwessel/feat_userkey_create
feat: Allow user multiple keys with userkey create
2021-03-26 16:27:43 +01:00
Manfred Touron
84a0a31eda Merge pull request #253 from jwessel/feat_postgres
feat: Add postgres support
2021-03-26 16:26:05 +01:00
Manfred Touron
40bbea590c Merge pull request #248 from jwessel/master
feat: Allow removal by user for 'userkey rm'
2021-03-26 16:18:27 +01:00
Manfred Touron
e455d50db9 Merge pull request #251 from jwessel/feat_user_udpate
feat: Allow for update or removal of the invite token
2021-03-26 16:11:28 +01:00
Manfred Touron
be3f215e24 Merge pull request #256 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.16.2
2021-03-26 11:46:56 +01:00
Renovate Bot
c290253546 chore(deps): update all docker tags to v1.16.2 2021-03-12 02:24:27 +00:00
Jason Wessel
28a5fd1846 feat: Allow user multiple keys with userkey create
And end user may have more than one ssh key, the userkey create
command should be able to accept more than one key so you can do
something like:

   curl https://github.com/USER.keys | ssh sshportal -p 2222 -l admin userkey create USER

The userkey create command also does not work properly from an
interactive shell due to the use of bufio.  This patch adds the
ability to use either the interactive shell or direct ssh command to
input one or more keys.

Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
2021-03-09 09:29:10 -06:00
Jason Wessel
19605f0054 feat: Add postgres support
Postgres is more picky about submitting a string to the id column in a
table.  Postgres requires the use of only integers for the array of
values in a select statement containing: where id IN (...array...)

This patch fixes all the following class of problems:

   SELECT * FROM "ssh_keys" WHERE
   "ssh_keys"."deleted_at" IS NULL AND ((id IN ('host')) OR (name IN
   ('host'))) ORDER BY "ssh_keys"."id" ASC LIMIT 1 [0 rows affected or
   returned ] error: pq: invalid input syntax for
   type integer: "host"

Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
2021-03-09 08:57:08 -06:00
Jason Wessel
5b4332072c feat: Allow for update or removal of the invite token
If the invite leaks for the admin user it is possible for the admin
user to be compromised by another invite request.  It needs to be
possible to entirely remove the invite capability for any given user.

New arguments added to user update:

   --invite_token value, -i value            Updates the invite token
   --remove_invite, -R                       Remove invite token

Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
2021-03-09 08:51:45 -06:00
Jason Wessel
c1c4c556b4 feat: Allow removal by user for 'userkey rm'
The userkey rm command implies that it can remove a key by user or the
id key, but it only works against the data base id of the key.  This
patch allows the userkey rm command to work with the user name, so
that all the keys for the user can be cleared out in one command.

Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
2021-03-08 11:22:38 -06:00
Jason Wessel
3c32177213 fix: host inspect causes db errors with later operations
The most simple case with a fresh install of sshportal using the
following commands put the shell into a unrecoverable state.

config> host create test1@test1
1
config> host inspect 1
config> host create test2@test2
error: can't preload field Groups for dbmodels.SSHKey

The issue is caused because the global db handle is replaced with the
inspect command.

Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
2021-03-01 07:37:15 -08:00
Manfred Touron
762736d622 Merge pull request #216 from jle64/ecdsa 2021-02-28 07:46:06 +01:00
Manfred Touron
bbbc484fe8 Merge pull request #247 from moul/dependabot/github_actions/golangci/golangci-lint-action-v2.5.1
chore(deps): bump golangci/golangci-lint-action from v2.3.0 to v2.5.1
2021-02-28 07:44:57 +01:00
Manfred Touron
e1602364c8 Merge pull request #235 from moul/dependabot/go_modules/github.com/gliderlabs/ssh-0.3.2
chore(deps): bump github.com/gliderlabs/ssh from 0.3.1 to 0.3.2
2021-02-28 07:44:52 +01:00
Manfred Touron
2540d1e861 Merge pull request #241 from moul/dependabot/go_modules/github.com/olekukonko/tablewriter-0.0.5
chore(deps): bump github.com/olekukonko/tablewriter from 0.0.4 to 0.0.5
2021-02-28 07:44:48 +01:00
Manfred Touron
177a198420 Merge pull request #244 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.16.0
2021-02-28 07:44:44 +01:00
Manfred Touron
51612aab13 Merge pull request #245 from moul/dependabot/docker/golang-1.16.0
chore(deps): bump golang from 1.15.8 to 1.16.0
2021-02-28 07:44:38 +01:00
dependabot[bot]
e20af1dde5 chore(deps): bump golangci/golangci-lint-action from v2.3.0 to v2.5.1
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from v2.3.0 to v2.5.1.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v2.3.0...d9f0e73c0497685d68af8c58280f49fcaf0545ff)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-25 04:18:52 +00:00
Renovate Bot
6caa1f1657 chore(deps): update all docker tags to v1.16.0 2021-02-18 03:57:04 +00:00
dependabot[bot]
e0f76d15ec chore(deps): bump golang from 1.15.8 to 1.16.0
Bumps golang from 1.15.8 to 1.16.0.

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-17 04:17:32 +00:00
matteyeux
05225a4b25 Fix typo in "shell commands" section in README.md 2021-02-15 11:27:12 +01:00
dependabot[bot]
bcc150727f chore(deps): bump github.com/olekukonko/tablewriter from 0.0.4 to 0.0.5
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 0.0.4 to 0.0.5.
- [Release notes](https://github.com/olekukonko/tablewriter/releases)
- [Commits](https://github.com/olekukonko/tablewriter/compare/v0.0.4...v0.0.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-11 04:21:57 +00:00
Manfred Touron
9062417d13 Merge pull request #237 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.15.8
2021-02-07 12:12:52 +01:00
Manfred Touron
baeade4043 Merge pull request #238 from moul/dependabot/docker/golang-1.15.8
chore(deps): bump golang from 1.15.7 to 1.15.8
2021-02-07 12:12:41 +01:00
Renovate Bot
b9552e98b5 chore(deps): update all docker tags to v1.15.8 2021-02-06 02:14:21 +00:00
dependabot[bot]
715ccde829 chore(deps): bump golang from 1.15.7 to 1.15.8
Bumps golang from 1.15.7 to 1.15.8.

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-05 04:16:20 +00:00
dependabot[bot]
f5dc1bd1b9 chore(deps): bump github.com/gliderlabs/ssh from 0.3.1 to 0.3.2
Bumps [github.com/gliderlabs/ssh](https://github.com/gliderlabs/ssh) from 0.3.1 to 0.3.2.
- [Release notes](https://github.com/gliderlabs/ssh/releases)
- [Commits](https://github.com/gliderlabs/ssh/compare/v0.3.1...v0.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-02 04:22:33 +00:00
Jonathan Lestrelin
c79c50aeb6 Remove go versions with missing requirements for ecdsa/ed2519 from CI. 2021-01-25 18:21:44 +01:00
Manfred Touron
df3542c6ee Merge pull request #233 from moul/dependabot/docker/golang-1.15.7
chore(deps): bump golang from 1.15.6 to 1.15.7
2021-01-25 14:22:48 +01:00
Manfred Touron
e40f5307a3 Merge pull request #232 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.15.7
2021-01-25 14:22:04 +01:00
Renovate Bot
6e6045306b chore(deps): update all docker tags to v1.15.7 2021-01-21 00:14:55 +00:00
dependabot[bot]
874467b1e6 chore(deps): bump golang from 1.15.6 to 1.15.7
Bumps golang from 1.15.6 to 1.15.7.

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-20 04:03:40 +00:00
Jonathan Lestrelin
5c1c559a9a Merge remote-tracking branch 'upstream/master' into ecdsa 2021-01-12 08:18:28 +01:00
Manfred Touron
6872c727ef Merge pull request #231 from moul/dev/moul/maintenance
chore: repo maintenance 🤖
2021-01-02 10:51:40 +01:00
moul-bot
cae996d041 chore: repo maintenance 🤖
more details: https://github.com/moul/repoman

Signed-off-by: moul-bot <bot@moul.io>
2021-01-01 15:24:08 +01:00
Manfred Touron
a23b77282c Merge pull request #229 from moul/renovate/docker-all
chore(deps): update circleci/golang docker tag to v1.15.6
2020-12-27 12:12:16 +01:00
Manfred Touron
24814c4152 Merge pull request #230 from moul/dependabot/docker/golang-1.15.6
chore(deps): bump golang from 1.15.5 to 1.15.6
2020-12-26 19:01:22 +01:00
Renovate Bot
07359988d0 chore(deps): update all docker tags to v1.15.6 2020-12-05 00:51:28 +00:00
dependabot[bot]
db6eb63297 chore(deps): bump golang from 1.15.5 to 1.15.6
Bumps golang from 1.15.5 to 1.15.6.

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-04 04:22:41 +00:00
Manfred Touron
5fdb31b97d Merge pull request #221 from moul/dependabot/github_actions/golangci/golangci-lint-action-v2.3.0
chore(deps): bump golangci/golangci-lint-action from v0.1.7 to v2.3.0
2020-11-15 21:48:47 +01:00
Manfred Touron
bce6b1998b Merge pull request #220 from moul/dependabot/github_actions/actions/cache-v2.1.3
chore(deps): bump actions/cache from v1 to v2.1.3
2020-11-15 21:48:34 +01:00
dependabot[bot]
f7fa60da97 chore(deps): bump golangci/golangci-lint-action from v0.1.7 to v2.3.0
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from v0.1.7 to v2.3.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v0.1.7...e868220d9fd3b523f1a8fcfb69749e8c7521ba14)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-15 20:40:22 +00:00
Manfred Touron
d2cd6b64a3 Merge pull request #215 from moul/renovate/all
chore(deps): update all
2020-11-15 21:39:39 +01:00
Manfred Touron
1ef0cc8725 Merge pull request #225 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.15.5
2020-11-15 21:38:37 +01:00
Renovate Bot
d894005c3f chore(deps): update all docker tags to v1.15.5 2020-11-13 01:41:26 +00:00
Renovate Bot
af7206d114 chore(deps): update all 2020-11-12 16:47:33 +00:00
dependabot[bot]
1f9d962cd6 chore(deps): bump actions/cache from v1 to v2.1.3
Bumps [actions/cache](https://github.com/actions/cache) from v1 to v2.1.3.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v1...0781355a23dac32fd3bac414512f4b903437991a)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-11 01:04:31 +00:00
Manfred Touron
460041c6e3 Merge pull request #219 from moul/dev/moul/maintenance
chore: repo maintenance 🤖
2020-11-11 02:04:08 +01:00
moul-bot
7068565ab1 chore: repo maintenance 🤖
more details: https://github.com/moul/repoman

Signed-off-by: moul-bot <bot@moul.io>
2020-11-09 22:53:21 +01:00
Manfred Touron
74bd885c1d Merge pull request #218 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.15.4
2020-11-07 16:54:04 +01:00
Renovate Bot
9317f206d1 chore(deps): update all docker tags to v1.15.4 2020-11-07 00:30:51 +00:00
Jonathan Lestrelin
6c3f803dc6 Add generation of ecdsa and ed25519 keys.
Make RSA keys use value from --length parameter.
Set default length when --length is unspecified based on key type.
Change default key format to ed25519 both in shell and for keys created
at initialization.
2020-10-10 04:21:11 +02:00
Renovate Bot
9c3d29eb83 chore(deps): update module gliderlabs/ssh to v0.3.1 2020-10-07 19:19:15 +00:00
Manfred Touron
e339a73931 Merge pull request #214 from moul/dev/moul/bump-deps4
chore: bump deps
2020-10-04 10:51:32 +02:00
Manfred Touron
0dcab1b380 chore: bump deps 2020-10-04 10:18:07 +02:00
Sergey Yashchuk
c697c9aaeb dev: ACLs external command hook 2020-09-18 01:24:51 +07:00
Manfred Touron
032f802348 Merge pull request #208 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.15.2
2020-09-14 20:57:45 +02:00
Renovate Bot
7fd9be9058 chore(deps): update all docker tags to v1.15.2 2020-09-10 00:31:00 +00:00
Manfred Touron
83b54aeeff Merge pull request #205 from moul/dev/moul/go115
chore: go1.15
2020-08-19 20:28:55 +02:00
Manfred Touron
2323d6fd1e chore: go1.15 2020-08-19 19:33:18 +02:00
Manfred Touron
4c947ce391 Merge pull request #204 from GreyOBox/increase-size-of-name-fields
fix: increase size of name fields
2020-08-19 18:27:33 +02:00
Sergey Yashchuk
44559f0547 fix: increase size of name fields 2020-08-19 18:23:36 +02:00
Manfred Touron
8234119cd4 Merge pull request #197 from moul/renovate/all
chore(deps): update golang.org/x/crypto commit hash to 123391f
2020-08-10 18:22:45 +02:00
Manfred Touron
7a75c13ac4 Merge pull request #200 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.14.7
2020-08-10 18:22:15 +02:00
Manfred Touron
4b10131790 Merge pull request #201 from moul/imgbot
[ImgBot] Optimize images
2020-08-09 17:14:22 +02:00
Manfred Touron
a29c6e8338 chore: add intro image 2020-08-09 00:30:36 +02:00
ImgBotApp
198e0717b5 [ImgBot] Optimize images
*Total -- 887.71kb -> 587.48kb (33.82%)

/.assets/bastion.jpg -- 503.44kb -> 249.40kb (50.46%)
/.assets/flow-diagram.png -- 104.11kb -> 79.45kb (23.69%)
/.assets/overview.png -- 32.65kb -> 26.50kb (18.82%)
/.assets/cluster-mysql.svg -- 8.50kb -> 7.08kb (16.74%)
/.assets/overview.svg -- 9.23kb -> 8.03kb (13.03%)
/.assets/flow-diagram.svg -- 13.85kb -> 12.39kb (10.51%)
/.assets/sql-schema.svg -- 36.89kb -> 33.99kb (7.85%)
/.assets/demo.gif -- 179.03kb -> 170.63kb (4.69%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2020-08-08 22:28:50 +00:00
Manfred Touron
d8fa2f6925 Add files via upload 2020-08-09 00:28:34 +02:00
Renovate Bot
16c8c0092e chore(deps): update all docker tags to v1.14.7 2020-08-08 00:28:27 +00:00
Renovate Bot
b0dfff2d90 chore(deps): update golang.org/x/crypto commit hash to 123391f 2020-07-28 20:38:36 +00:00
Manfred Touron
9d2badf253 Merge pull request #196 from moul/dev/moul/pr-194 2020-07-23 18:55:42 +02:00
Konstantin Bakaras
428344da17 feat: MySQL, Postgres support
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
2020-07-23 18:50:42 +02:00
Konstantin Bakaras
0c07ac790a feat: ACL Check with inception and expiration
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
2020-07-23 18:50:35 +02:00
Konstantin Bakaras
365a37959a chore: Model and edit
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
2020-07-23 18:50:35 +02:00
Manfred Touron
90fd6057cf Merge pull request #193 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.14.6
2020-07-23 18:24:07 +02:00
Manfred Touron
4220f3fb89 Merge pull request #190 from moul/renovate/all
chore(deps): update all
2020-07-23 17:22:27 +02:00
Renovate Bot
3e2acfc992 chore(deps): update all 2020-07-20 06:19:00 +00:00
Renovate Bot
9c464b2610 chore(deps): update all docker tags to v1.14.6 2020-07-18 00:55:40 +00:00
Manfred Touron
5760aece65 Merge pull request #192 from moul/dev/moul/maintenance
chore: repo maintenance 🤖
2020-07-12 14:08:51 +02:00
moul-bot
a24e20252a chore: repo maintenance 🤖
more details: https://github.com/moul/repoman

Signed-off-by: moul-bot <bot@moul.io>
2020-07-12 14:04:09 +02:00
Manfred Touron
37a7fa1917 Merge pull request #189 from moul/renovate/all
chore(deps): update all
2020-07-09 17:47:46 +02:00
Renovate Bot
f1b28b0363 chore(deps): update all 2020-07-08 08:14:45 +00:00
Manfred Touron
e43bb55e70 Merge pull request #188 from moul/dev/moul/fix-166 2020-07-08 10:12:52 +02:00
Manfred Touron
763ced7524 feat: host logging modes (disabled, commands, everything)
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
2020-07-04 22:16:46 +02:00
Manfred Touron
54128beb12 chore: point CHANGELOG.md to releases page
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
2020-07-04 21:53:26 +02:00
Manfred Touron
64ba179cc7 chore: add .gitattributes
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
2020-07-04 21:53:26 +02:00
Manfred Touron
bbdb4851a5 Merge pull request #187 from moul/dev/moul/maintenance
chore: repo maintenance 🤖
2020-07-04 01:59:48 +02:00
moul-bot
63719ec00e chore: repo maintenance 🤖
more details: https://github.com/moul/repoman

Signed-off-by: moul-bot <bot@moul.io>
2020-07-02 00:41:50 +02:00
Manfred Touron
0722497336 Update README.md 2020-07-01 14:51:57 +02:00
Manfred Touron
e74f7221b5 Merge pull request #168 from moul/dev/moul/linters
fix: add more linters
2020-07-01 14:51:03 +02:00
Manfred Touron
f4fc3a90bc fix: add more linters
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
2020-07-01 14:46:23 +02:00
Manfred Touron
df3aa6e165 Merge pull request #181 from moul/renovate/all
chore(deps): update all
2020-07-01 14:46:01 +02:00
Manfred Touron
986bcd7971 Merge pull request #185 from moul/dev/moul/windows 2020-07-01 14:17:50 +02:00
Manfred Touron
7f3ea431a1 Merge pull request #183 from jrrdev/exit_fix 2020-07-01 14:16:14 +02:00
Manfred Touron
dae0252857 chore: small fix for build on windows 2020-07-01 14:10:50 +02:00
Manfred Touron
33b8e5272c Merge pull request #184 from moul/dev/moul/renovate-tidy
chore: tell renovate to run go mod tidy
2020-07-01 14:10:15 +02:00
Manfred Touron
21e73757ac chore: tell renovate to run go mod tidy 2020-07-01 14:01:14 +02:00
jerard@alfa-safety.fr
bcb5d3b7ef fixup! Fix early closure of data stream. 2020-06-30 18:28:47 +02:00
jerard@alfa-safety.fr
d2f3f460b2 Fix early closure of data stream.
Closes moul/sshportal#55 and closes moul/sshportal#127
2020-06-30 10:28:39 +02:00
Renovate Bot
e06fe6f5a3 chore(deps): update all 2020-06-22 22:25:28 +00:00
Manfred Touron
fb9dabfe6b chore: bump deps 2020-06-09 12:11:41 +02:00
Manfred Touron
0e0cd8fed5 Merge pull request #180 from moul/dev/moul/update-project-layout
fix: update project layout
2020-06-09 12:10:17 +02:00
Manfred Touron
8959e1782f fix: update project layout
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
2020-06-09 11:45:45 +02:00
Manfred Touron
33151105e0 Merge pull request #173 from moul/renovate/all
chore(deps): update all
2020-06-09 11:07:15 +02:00
Manfred Touron
77b40eb9ed Merge pull request #177 from NocFlame/master
Update README.md; Info about missing host keys
2020-06-09 11:07:01 +02:00
Manfred Touron
075dfd0aa7 Merge pull request #176 from Zatte/update_readme_shell_conditions
Update readme; special host names
2020-06-09 11:06:38 +02:00
Manfred Touron
5cf6b1c218 Merge pull request #174 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.14.4
2020-06-06 18:21:26 +02:00
Renovate Bot
6527746a91 chore(deps): update all 2020-06-04 21:46:38 +00:00
Renovate Bot
020ca9c6b3 chore(deps): update all docker tags to v1.14.4 2020-06-03 00:16:26 +00:00
NocFlame
8c7831480b Update README.md
*What this PR does / why we need it:*
Current readme does not inform user of how to fix the behaviour of password promt when linking admin account

*Which issue this PR fixes: fixes #128*
Updated the readme to inform the user of a quick fix (ssh-keygen -t rsa) when association fails and user is asked for password
2020-05-28 11:12:28 +02:00
Mikael Rapp
e399dfd8e4 Update readme; special host names 2020-05-27 23:30:28 +02:00
Manfred Touron
be83c7148d Merge pull request #172 from moul/dev/moul/bump-deps4 2020-05-12 03:35:22 +02:00
Manfred Touron
ce187e8675 fix: bump deps 2020-05-12 03:30:27 +02:00
Manfred Touron
f13ede4ba7 Update README.md 2020-05-12 03:27:15 +02:00
Manfred Touron
fb061ed419 Merge pull request #138 from moul/renovate/all
Update all
2020-05-10 15:45:44 +02:00
Renovate Bot
b4a377f269 Update all 2020-05-02 08:14:28 +00:00
Manfred Touron
de6f37aa64 Merge pull request #170 from moul/renovate/docker-all
Update all Docker tags to v1.14.2
2020-04-10 10:11:05 +02:00
Renovate Bot
32219577b8 Update all Docker tags to v1.14.2 2020-04-10 00:21:11 +00:00
Manfred Touron
abc7329a71 Merge pull request #167 from moul/renovate/docker-all
Update all Docker tags to v1.14.1
2020-03-22 14:58:30 +01:00
Renovate Bot
675942e967 Update all Docker tags to v1.14.1 2020-03-21 00:28:54 +00:00
Manfred Touron
5b20cd501e Update README.md
See https://github.com/moul/sshportal/issues/164
2020-03-10 11:01:26 +01:00
Manfred Touron
b6aaf4d7cf Update README.md 2020-03-05 15:33:52 +01:00
Manfred Touron
972e232559 Merge pull request #143 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.14.0
2020-03-01 17:17:37 +01:00
Manfred Touron
851a91b1a0 fix: fix deps 2020-02-28 22:12:46 +01:00
Renovate Bot
6a068dc430 chore(deps): update all docker tags to v1.14.0 2020-02-28 22:05:37 +01:00
Manfred Touron
2cdfcf60fe Merge pull request #163 from MitaliBo/master
Security fix for github.com/moby/moby
2020-02-28 22:04:24 +01:00
Manfred Touron
5d9e0c367a feat: use secure rand seed 2020-02-25 11:49:38 +01:00
MitaliBo
cbf8263033 Update go.mod 2020-02-24 14:27:29 -08:00
Manfred Touron
846c73d9bc Merge pull request #160 from moul/dev/moul/bump-deps
chore: bump-deps
2020-02-22 20:51:29 +01:00
Manfred Touron
e0b43b1976 Merge pull request #162 from moul/dev/moul/semantic-release
feat: set up semantic release
2020-02-22 20:48:51 +01:00
Manfred Touron
6a6e788968 feat: set up semantic release 2020-02-22 20:43:54 +01:00
Manfred Touron
4754cad42a chore: bump-deps 2020-02-18 00:12:56 +01:00
Manfred Touron
db58e53f3b Merge pull request #156 from jeanlouisferey/master
Little correction
2020-02-18 00:06:27 +01:00
Manfred Touron
b31acb4348 Merge pull request #148 from jle64/fix_shell_connection_log
Fix db user id type in shell connection log.
2020-02-18 00:06:20 +01:00
Manfred Touron
c794c2c076 chore: add funding + update go.sum 2020-02-18 00:05:35 +01:00
Jonathan Lestrelin
42d6cd44bb Fix db user id type in shell connection log. 2020-02-17 23:55:35 +01:00
Manfred Touron
f9057ca56a Merge pull request #157 from bozzo/master
Add Helm chart to deploy SSHPortal on Kubernetes
2020-02-17 23:54:39 +01:00
bozzo
c2f1999037 Set theme jekyll-theme-slate 2020-02-17 09:08:51 +01:00
bozzo
44b386f7a7 Add Helm chart to deploy SSHPortal on Kubernetes
Support HA with Mysql database
Support autoscaling
2020-02-08 23:06:19 +01:00
Jean-Louis Férey
89b296db4e Little correction 2020-01-29 16:35:39 +01:00
Manfred Touron
c16403fb3f Merge pull request #146 from opencollective/opencollective
Activating Open Collective
2019-09-18 09:11:08 +02:00
Jess
5e21fb72e6 Added financial contributors to the README 2019-09-17 18:02:58 -07:00
Manfred Touron
c5681bf880 chore: post-release version bump 2019-06-24 11:43:32 +02:00
Manfred Touron
db85d6545d v1.10.0 2019-06-24 11:31:32 +02:00
Manfred Touron
9912c3deba chore: setup goreleaser 2019-06-24 10:57:47 +02:00
Manfred Touron
fc5c342e40 chore: bump github.com/gliderlabs/ssh to v0.2.2 (#141)
chore: bump github.com/gliderlabs/ssh to v0.2.2
2019-06-24 10:28:51 +02:00
Manfred Touron
60707b3faa chore: update CHANGELOG 2019-06-24 10:25:04 +02:00
Manfred Touron
f36845ac6b chore: bump github.com/gliderlabs/ssh to v0.2.2 2019-06-24 10:25:04 +02:00
Manfred Touron
9f76bd6cad Merge pull request #131 from moul/renovate/all
Update all
2019-06-14 11:26:07 +02:00
Manfred Touron
c53d5d9964 Merge pull request #137 from moul/renovate/docker-all
Update all Docker tags to v1.12.6
2019-06-14 11:25:21 +02:00
Renovate Bot
171d461ea5 Update all 2019-06-14 08:29:13 +00:00
Renovate Bot
b2b04a1155 Update all Docker tags to v1.12.6 2019-06-13 00:27:25 +00:00
Manfred Touron
671ba03b78 Update README.md 2019-06-10 09:57:20 +02:00
Manfred Touron
9095725778 Update README.md 2019-06-10 09:57:05 +02:00
Manfred Touron
8b2e5daba3 Set log files mode to 440 instead of 640. (#134)
Set log files mode to 440 instead of 640.
2019-06-10 09:54:53 +02:00
Jonathan Lestrelin
75b7a5f571 Set log files mode to 440 instead of 640. 2019-06-10 09:40:25 +02:00
Manfred Touron
4b9e881ad0 Merge pull request #135 from jle64/accept_ip_as_hostname
Allow to create a host using an ip as name
2019-06-09 15:39:41 +02:00
Manfred Touron
59f8f52cca Merge pull request #133 from jle64/better_logfile_name
Add username and session ID to session log filename.
2019-06-09 15:38:08 +02:00
Jonathan Lestrelin
4adaf83fd3 Accept to create a host using an ip as name by relaxing unix_user constraint on name and not splitting on . if an ip is detected. 2019-06-07 18:15:25 +02:00
Jonathan Lestrelin
84464a4ea6 Add username and session ID to session log filename. 2019-06-07 16:58:41 +02:00
Manfred Touron
cafac0b8b5 chore: fix CI (#132)
chore: fix CI
2019-06-06 16:35:45 +02:00
Manfred Touron
5346300a64 chore: fix CI 2019-06-06 16:32:49 +02:00
Manfred Touron
1d4554eabc chore: fixup 2019-06-06 16:32:22 +02:00
Manfred Touron
50bdba8b70 Update all (#110)
Update all
2019-05-30 20:40:22 +02:00
Manfred Touron
8c785f6dea Update all Docker tags to v1.12.5 (#130)
Update all Docker tags to v1.12.5
2019-05-30 20:40:01 +02:00
Renovate Bot
93e6abc9ba Update all 2019-05-28 19:27:01 +00:00
Renovate Bot
60d7c85c11 Update all Docker tags to v1.12.5 2019-05-21 20:28:40 +00:00
Manfred Touron
883bad2ee5 Merge pull request #124 from welderpb/master
[fix] unable to use encrypted ssh private keys
2019-03-29 07:11:08 +01:00
Manfred Touron
7d68e144f8 Merge pull request #116 from moul/renovate/docker-all
chore(deps): update all docker tags to v1.12.1
2019-03-29 07:08:36 +01:00
Manfred Touron
7f32e38cf8 Merge pull request #123 from vdaviot/master
[FIX] Format of id in new session | Closing channel if host unreachable
2019-03-29 07:07:36 +01:00
Renovate Bot
43a96d1636 chore(deps): update all docker tags to v1.12.1 2019-03-16 00:20:45 +00:00
welderpb
00e7d2e45d fix for encrypted ssh private keys 2019-03-05 17:04:05 +01:00
Valentin Daviot
2e711c3591 fixed format of id for new bastion session | applied fix from issue #56 to permit the closing of channel in case of unreachable host
Signed-off-by: Valentin Daviot <valentin.daviot@alterway.fr>
2019-03-01 16:04:27 +01:00
Manfred Touron
5d147fc03b Merge pull request #118 from nellyasher/master
Readme updates
2019-02-01 13:38:07 +01:00
Manfred Touron
3dccefbbcb chore: use png + add toc 2019-02-01 13:36:36 +01:00
Nelly Asher
7c4995fa4a Update README.md 2019-02-01 13:34:18 +01:00
Nelly Asher
2b8f051414 TOC added, chapters rearranged, "Use cases" added. 2019-02-01 13:34:18 +01:00
Manfred Touron
4e17c81d63 chore: switch to png for images in the readme 2019-02-01 12:57:18 +01:00
Manfred Touron
8b4b677d6a chore: update flow diagram + add png outputs 2019-02-01 12:56:07 +01:00
Manfred Touron
47229bf473 chore: update flow diagram style 2019-02-01 11:23:02 +01:00
Manfred Touron
ec5b567da9 Update README.md 2019-01-04 01:11:10 +01:00
Manfred Touron
03b59fae1c Merge pull request #113 from ahhx/master
refactor: split package main
2019-01-04 00:58:24 +01:00
Manfred Touron
ede8b3ecf2 chore: fix lint issues 2019-01-04 00:56:06 +01:00
Manfred Touron
7ae90b9199 build: fix deps checksums 2019-01-04 00:51:54 +01:00
ahh
a651da451e refactor: split package main
sshportal refactor. Focused on splitting up package main into packages
main, dbmodels, crypto, and bastion.
2019-01-03 21:11:43 +01:00
Manfred Touron
f220af5c54 Merge pull request #111 from shawn111/missing_authorized_key
Fix userkey create.
2019-01-03 19:57:19 +01:00
Shawn Wang
eebf987900 Fix UserKey.Key for govalidator.ValidateStruct.
UserKey.Key is a []byte field.
The valid: "required" would cause wrong Validation.
2018-12-13 10:46:05 +00:00
Shawn Wang
3d5101011f Fix userkey create, missing AuthorizedKey 2018-12-12 23:39:25 +08:00
Manfred Touron
2cdc19dfdd Merge pull request #109 from moul/moul-patch-1
Update renovate.json
2018-12-07 14:53:50 +01:00
Manfred Touron
d8a7b1e16c Update renovate.json 2018-12-07 14:49:50 +01:00
Manfred Touron
d7490d089c Merge pull request #102 from moul/dev/mou/bump-deps2
chore: bump deps
2018-11-25 09:46:57 +01:00
Manfred Touron
774c6c0f64 chore: bump deps 2018-11-25 09:43:28 +01:00
Manfred Touron
92d11c53de Merge pull request #98 from Raerten/env-variables
use Environment Variables for settings
2018-11-25 09:41:09 +01:00
Manfred Touron
c509f65a27 Merge pull request #101 from moul/dev/moul/windows-support
fix a build constraint
2018-11-25 09:40:00 +01:00
Дмитрий Шульгачик
20b9e839d3 fix a build constraint 2018-11-25 09:34:41 +01:00
Дмитрий Шульгачик
6f4fb24cd0 update mysql example using ENV 2018-11-19 14:56:24 +03:00
Дмитрий Шульгачик
23a89fe1de Add ability to set DB driver and DB credentials from environment variables 2018-11-19 14:49:31 +03:00
Manfred Touron
559df1f523 Merge pull request #96 from moul/dev/moul/bump-deps
chore: bump deps
2018-11-18 15:58:07 +01:00
Manfred Touron
ad2b8ebc38 chore: bump deps 2018-11-18 15:54:11 +01:00
Manfred Touron
3824629d4d Post-release version bump 2018-11-18 15:48:42 +01:00
70 changed files with 3063 additions and 2227 deletions

BIN
.assets/bastion.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

BIN
.assets/cluster-mysql.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 171 KiB

View File

@@ -1,12 +1,12 @@
digraph {
rankdir=LR;
layout=dot;
node[shape=record];
start[label="ssh sshportal";color=blue;fontcolor=blue;fontsize=20];
node[shape=record;style=rounded;fontname="helvetica-bold"];
graph[layout=dot;rankdir=LR;overlap=prism;splines=true;fontname="helvetica-bold"];
edge[arrowhead=none;fontname="helvetica"];
start[label="\$\> ssh sshportal";color=blue;fontcolor=blue;fontsize=18];
subgraph cluster_sshportal {
graph[fontsize=20;style=dashed;color=purple;fontcolor=purple];
graph[fontsize=18;color=gray;fontcolor=black];
label="sshportal";
{
node[color=darkorange;fontcolor=darkorange];
@@ -17,25 +17,25 @@ digraph {
}
{
node[color=darkgreen;fontcolor=darkgreen];
builtin_shell[label="built-in shell"];
ssh_proxy[label="SSH proxy"];
learn_key[label="learn key"];
builtin_shell[label="built-in\nconfig shell"];
ssh_proxy[label="SSH proxy\nJump-Host"];
learn_key[label="learn the\npub key"];
}
err_and_exit[label="error and exit";color=red;fontcolor=red];
err_and_exit[label="\nerror\nand exit\n\n";color=red;fontcolor=red];
{ rank=same; ssh_proxy; builtin_shell; learn_key; err_and_exit; }
{ rank=same; known_user_key; unknown_user_key; }
}
subgraph cluster_hosts {
label="your hosts";
graph[fontsize=20;style=dashed;color=purple;fontcolor=purple];
graph[fontsize=18;color=gray;fontcolor=black];
node[color=blue;fontcolor=blue];
host_1[label="root@host1"];
host_2[label="user@host2:2222"];
host_3[label="root@host3:1234"];
}
{
edge[color=blue];
start -> known_user_key;
@@ -53,13 +53,13 @@ digraph {
{
edge[color=darkorange;fontcolor=darkorange];
known_user_key -> acl_manager[label="user matches an existing host"];
unknown_user_key -> invite_manager[headlabel="user=invite:<token>"];
unknown_user_key -> invite_manager[label="user=invite:<token>";labelloc=b];
}
{
edge[color=red;fontcolor=red];
known_user_key -> err_and_exit[label="invalid user"];
acl_manager -> err_and_exit[label="unauthorized"];
unknown_user_key -> err_and_exit[label="any other user"];
invite_manager -> err_and_exit[label="invalid token"];
unknown_user_key -> err_and_exit[label="any other user";constraint=false];
invite_manager -> err_and_exit[label="invalid token";constraint=false];
}
}

BIN
.assets/flow-diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
.assets/overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,7 +1,7 @@
defaults: &defaults
working_directory: /go/src/moul.io/sshportal
docker:
- image: circleci/golang:1.11
- image: circleci/golang:1.16.2
environment:
GO111MODULE: "on"
@@ -16,18 +16,6 @@ install_retry: &install_retry
version: 2
jobs:
go.build:
<<: *defaults
steps:
- checkout
- *install_retry
- run: /tmp/retry -m 3 go mod download
- run: /tmp/retry -m 3 go mod vendor
- run: /tmp/retry -m 3 make install
- run: GO111MODULE=off /tmp/retry -m 3 go test -v ./...
- run: /tmp/retry -m 3 curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.12.2
- run: PATH=$PATH:$(pwd)/bin /tmp/retry -m 3 make lint
docker.integration:
<<: *defaults
steps:
@@ -36,9 +24,10 @@ jobs:
name: Install Docker Compose
command: |
umask 022
curl -L https://github.com/docker/compose/releases/download/1.11.2/docker-compose-`uname -s`-`uname -m` > ~/docker-compose
curl -L https://github.com/docker/compose/releases/download/1.11.4/docker-compose-`uname -s`-`uname -m` > ~/docker-compose
- setup_remote_docker:
docker_layer_caching: true
version: 18.09.3 # https://github.com/golang/go/issues/40893
- *install_retry
- run: /tmp/retry -m 3 docker build -t moul/sshportal .
- run: /tmp/retry -m 3 make integration
@@ -48,6 +37,4 @@ workflows:
version: 2
build_and_integration:
jobs:
- go.build
- docker.integration
# requires: docker.build?

View File

@@ -1,3 +1,5 @@
# .git/ # should be kept for git-based versionning
examples/
.circleci/
.assets/

17
.gitattributes vendored Normal file
View File

@@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Collapse vendored and generated files on GitHub
AUTHORS linguist-generated
vendor/* linguist-vendored
rules.mk linguist-vendored
*/vendor/* linguist-vendored
*.gen.* linguist-generated
*.pb.go linguist-generated
*.pb.gw.go linguist-generated
go.sum linguist-generated
go.mod linguist-generated
gen.sum linguist-generated
# Reduce conflicts on markdown files
*.md merge=union

6
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
github: ["moul"]
patreon: moul
open_collective: sshportal
custom:
- "https://www.buymeacoffee.com/moul"
- "https://manfred.life/donate"

View File

@@ -1,25 +1,15 @@
<!-- Thanks for filling an issue!
### Actual Result / Problem
If this is a BUG REPORT, please:
- Fill in as much of the template below as you can
When I do Foo, Bar happens...
If this is a FEATURE REQUEST, please:
- Describe *in detail* the feature/behavior/change you would like to see
-->
### Expected Result / Suggestion
**What happened**:
I expect that Foobar happens...
**What you expected to happen**:
### Some context
**How to reproduce it (as minimally and precisely as possible)**:
**Anything else we need to know?**:
<!--
**Environment**:
- sshportal --version
- ssh sshportal info
- OS (e.g. from /etc/os-release):
- install method (e.g. go/docker/brew/...):
- others:
-->
Any screenshot to share?
`sshportal --version`?
`ssh sshportal info`?
OS/Go version?
...

View File

@@ -1,7 +1 @@
<!-- Thanks for sending a pull request! Here are some tips for you -->
**What this PR does / why we need it**:
**Which issue this PR fixes**: fixes #xxx, fixes #xxx...
**Special notes for your reviewer**:
<!-- thank you for your contribution! ❤️ -->

20
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: docker
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10

7
.github/renovate.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"extends": [
"config:base"
],
"groupName": "all",
"gomodTidy": true
}

87
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,87 @@
name: CI
on:
push:
tags:
- v*
branches:
- master
pull_request:
jobs:
docker-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the Docker image
run: docker build . --file Dockerfile
golangci-lint:
name: golangci-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: lint
uses: golangci/golangci-lint-action@v2.5.2
with:
version: v1.38
github-token: ${{ secrets.GITHUB_TOKEN }}
tests-on-windows:
needs: golangci-lint # run after golangci-lint action to not produce duplicated errors
runs-on: windows-latest
strategy:
matrix:
golang:
- 1.16.x
steps:
- uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.golang }}
- name: Run tests on Windows
run: make.exe unittest
continue-on-error: true
tests-on-mac:
needs: golangci-lint # run after golangci-lint action to not produce duplicated errors
runs-on: macos-latest
strategy:
matrix:
golang:
- 1.16.x
steps:
- uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.golang }}
- uses: actions/cache@v2.1.5
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ matrix.golang }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.golang }}-
- name: Run tests on Unix-like operating systems
run: make unittest
tests-on-linux:
needs: golangci-lint # run after golangci-lint action to not produce duplicated errors
runs-on: ubuntu-latest
strategy:
matrix:
golang:
- 1.13.x
- 1.14.x
- 1.15.x
- 1.16.x
steps:
- uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.golang }}
- uses: actions/cache@v2.1.5
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ matrix.golang }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.golang }}-
- name: Run tests on Unix-like operating systems
run: make unittest

13
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Semantic Release
on: push
jobs:
semantic-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: codfish/semantic-release-action@v1
if: github.ref == 'refs/heads/master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

10
.gitignore vendored
View File

@@ -1,4 +1,12 @@
coverage.txt
dist/
*~
*#
.*#
.DS_Store
/log/
/sshportal
*.db
/data
/data
sshportal.history
.idea

View File

@@ -1,8 +1,10 @@
run:
deadline: 1m
tests: false
#skip-files:
# - ".*\\.gen\\.go"
skip-files:
- "testing.go"
- ".*\\.pb\\.go"
- ".*\\.gen\\.go"
linters-settings:
golint:
@@ -18,17 +20,36 @@ linters-settings:
linters:
disable-all: true
enable:
- goconst
- misspell
- bodyclose
- deadcode
- misspell
- structcheck
- depguard
- dogsled
#- dupl
- errcheck
- unused
- varcheck
- staticcheck
- unconvert
#- funlen
- gochecknoinits
#- gocognit
- goconst
- gocritic
#- gocyclo
- gofmt
- goimports
- golint
- gosimple
- govet
- ineffassign
- interfacer
#- maligned
- misspell
- nakedret
- prealloc
- scopelint
- staticcheck
- structcheck
#- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace

29
.goreleaser.yml Normal file
View File

@@ -0,0 +1,29 @@
builds:
-
goos: [linux, darwin]
goarch: [386, amd64, arm, arm64]
ldflags:
- -s -w -X main.GitSha={{.ShortCommit}} -X main.GitBranch=master -X main.GitTag={{.Version}}
archives:
- wrap_in_directory: true
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
brews:
-
name: sshportal
github:
owner: moul
name: homebrew-moul
commit_author:
name: moul-bot
email: "m+bot@42.am"
homepage: https://manfred.life/sshportal
description: "Simple, fun and transparent SSH (and telnet) bastion"

8
.releaserc.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
branch: 'master',
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/github',
],
};

39
AUTHORS generated Normal file
View File

@@ -0,0 +1,39 @@
# This file lists all individuals having contributed content to the repository.
# For how it is generated, see 'https://github.com/moul/rules.mk'
ahh <ahamidullah@gmail.com>
Alen Masic <alenn.masic@gmail.com>
Alexander Turner <me@alexturner.co>
bozzo <bozzo@users.noreply.github.com>
Darko Djalevski <darko.djalevski@inplayer.com>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fossabot <badges@fossa.io>
ImgBotApp <ImgBotHelp@gmail.com>
Jason Wessel <jason.wessel@windriver.com>
Jean-Louis Férey <jeanlouis.ferey@orange.com>
jerard@alfa-safety.fr <jrrdev@users.noreply.github.com>
Jess <jessachandler@gmail.com>
Jonathan Lestrelin <jonathan.lestrelin@gmail.com>
Julien Dessaux <julien.dessaux@adyxax.org>
Konstantin Bakaras <k.bakaras@voskhod.ru>
Manfred Touron <94029+moul@users.noreply.github.com>
Manfred Touron <m@42.am>
Manuel <manuel.sabban@nbs-system.com>
Manuel Sabban <manu@sabban.eu>
Manuel Sabban <msa@nbs-system.com>
Mathieu Pasquet <mathieu.pasquet@alterway.fr>
matteyeux <matteyeux@users.noreply.github.com>
Mikael Rapp <micke.rapp@gmail.com>
MitaliBo <mitali.bisht14@gmail.com>
moul-bot <bot@moul.io>
Nelly Asher <karmelylle@rambler.ru>
NocFlame <aad@nocflame.se>
Quentin Perez <qperez42@gmail.com>
Renovate Bot <bot@renovateapp.com>
Sergey Yashchuk <11705746+GreyOBox@users.noreply.github.com>
Sergey Yashchuk <sergey.yashchuk@coins.ph>
Shawn Wang <shawn111@gmail.com>
Valentin Daviot <valentin.daviot@alterway.fr>
valentin.daviot <valentin.daviot@alterway.fr>
welderpb <welderpb@users.noreply.github.com>
Дмитрий Шульгачик <tech@uniplug.ru>

View File

@@ -1,103 +1,3 @@
# Changelog
## v1.9.0 (2018-11-18)
* Add `hostgroup update` and `usergroup update` commands ([#58](https://github.com/moul/sshportal/pull/58)) by [@adyxax](https://github.com/adyxax)
* Add socket timeout ([#80](https://github.com/moul/sshportal/pull/80)) by [@ahhx](https://github.com/ahhx)
* Add a flag to list only active sessions ([#76](https://github.com/moul/sshportal/pull/76)) by [@vdaviot](https://github.com/vdaviot)
* Unset hop on host ([#74](https://github.com/moul/sshportal/pull/74)) by [@vdaviot](https://github.com/vdaviot)
* Fix session status and duration display ([#75](https://github.com/moul/sshportal/pull/75)) by [@vdaviot](https://github.com/vdaviot)
* Fix log path and filename on Windows ([#78](https://github.com/moul/sshportal/pull/78)) by [@Raerten](https://github.com/Raerten)
* Admin user is not editable ([#69](https://github.com/moul/sshportal/pull/69)) by [@alenn-m](https://github.com/alenn-m)
* Switch to go modules (go1.11) ([#83](https://github.com/moul/sshportal/pull/83))
* Switch to moul.io/sshportal canonical URL ([#86](https://github.com/moul/sshportal/pull/86))
* Switch to golangci-lint ([#87](https://github.com/moul/sshportal/pull/87))
## v1.8.0 (2018-04-02)
* The default created user now has the same username as the user starting sshportal (was hardcoded "admin")
* Add Telnet support
* Add TTY audit feature ([#23](https://github.com/moul/sshportal/issues/23)) by [@sabban](https://github.com/sabban)
* Fix `--assign-*` commands when using MySQL driver ([#45](https://github.com/moul/sshportal/issues/45))
* Add *HOP* support, an efficient and integrated way of using a jump host transparently ([#47](https://github.com/moul/sshportal/issues/47)) by [@mathieui](https://github.com/mathieui)
* Fix panic on some `ls` commands ([#54](https://github.com/moul/sshportal/pull/54)) by [@jle64](https://github.com/jle64)
* Add tunnels (`direct-tcp`) support with logging ([#44](https://github.com/moul/sshportal/issues/44)) by [@sabban](https://github.com/sabban)
* Add `key import` command ([#52](https://github.com/moul/sshportal/issues/52)) by [@adyxax](https://github.com/adyxax)
* Add 'exec' logging ([#40](https://github.com/moul/sshportal/issues/40)) by [@sabban](https://github.com/sabban)
## v1.7.1 (2018-01-03)
* Return non-null exit-code on authentication error
* **hotfix**: repair invite system (broken in v1.7.0)
## v1.7.0 (2018-01-02)
Breaking changes:
* Use `sshportal server` instead of `sshportal` to start a new server (nothing to change if using the docker image)
* Remove `--config-user` and `--healthcheck-user` global options
Changes:
* Fix connection failure when sending too many environment variables (fix [#22](https://github.com/moul/sshportal/issues/22))
* Fix panic when entering empty command (fix [#13](https://github.com/moul/sshportal/issues/13))
* Add `config backup --ignore-events` option
* Add `sshportal healthcheck [--addr=] [--wait] [--quiet]` cli command
* Add [Docker Healthcheck](https://docs.docker.com/engine/reference/builder/#healthcheck) helper
* Support Putty (fix [#24](https://github.com/moul/sshportal/issues/24))
## v1.6.0 (2017-12-12)
* Add `--latest` and `--quiet` options to `ls` commands
* Add `healthcheck` user
* Add `key show KEY` command
## v1.5.0 (2017-12-02)
* Create Session objects on each connections (history)
* Connection history
* Audit log
* Add dynamic strict host key checking (learning on the first time, strict on the next ones)
* Add-back MySQL support (experimental)
* Fix some backup/restore bugs
## v1.4.0 (2017-11-24)
* Add 'key setup' command (easy SSH key installation)
* Add Updated and Created fields in 'ls' commands
* Add `--aes-key` option to encrypt sensitive data
## v1.3.0 (2017-11-23)
* More details in 'ls' commands
* Add 'host update' command (fix [#2](https://github.com/moul/sshportal/issues/2))
* Add 'user update' command (fix [#3](https://github.com/moul/sshportal/issues/3))
* Add 'acl update' command (fix [#4](https://github.com/moul/sshportal/issues/4))
* Allow connecting to the shell mode with the registered username or email (fix [#5](https://github.com/moul/sshportal/issues/5))
* Add 'listhosts' role (fix [#5](https://github.com/moul/sshportal/issues/5))
## v1.2.0 (2017-11-22)
* Support adding multiple `--group` links on `host create` and `user create`
* Use govalidator to perform more consistent input validation
* Use a database migration system
## v1.1.0 (2017-11-15)
* Improve versionning (static VERSION + dynamic GIT_* info)
* Configuration management (backup + restore)
* Implement Exit (fix [#6](https://github.com/moul/sshportal/pull/6))
* Disable mysql support (not fully working right now)
* Set random seed properly
## v1.0.0 (2017-11-14)
Initial version
* Host management
* User management
* User Group management
* Host Group management
* Host Key management
* User Key management
* ACL management
* Connect to host using key or password
* Admin commands can be run directly or in an interactive shell
Here: https://github.com/moul/sshportal/releases

View File

@@ -1,7 +1,10 @@
# build
FROM golang:1.11 as builder
COPY . /go/src/moul.io/sshportal
FROM golang:1.16.2 as builder
ENV GO111MODULE=on
WORKDIR /go/src/moul.io/sshportal
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
RUN make _docker_install
# minimal runtime

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 Manfred Touron <m@42.am>
Copyright 2017-2021 Manfred Touron <m@42.am>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,18 +1,16 @@
GIT_SHA ?= $(shell git rev-parse HEAD)
GIT_TAG ?= $(shell git describe --tags --always)
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
LDFLAGS ?= -X main.GitSha=$(GIT_SHA) -X main.GitTag=$(GIT_TAG) -X main.GitBranch=$(GIT_BRANCH)
VERSION ?= $(shell grep 'VERSION =' main.go | cut -d'"' -f2)
GOPKG ?= moul.io/sshportal
GOBINS ?= .
DOCKER_IMAGE ?= moul/sshportal
VERSION ?= `git describe --tags --always`
VCS_REF ?= `git rev-parse --short HEAD`
GO_INSTALL_OPTS = -ldflags="-X main.GitSha=$(VCS_REF) -X main.GitTag=$(VERSION)"
PORT ?= 2222
include rules.mk
DB_VERSION ?= v$(shell grep -E 'ID: "[0-9]+",' pkg/bastion/dbinit.go | tail -n 1 | cut -d'"' -f2)
AES_KEY ?= my-dummy-aes-key
GO ?= GO111MODULE=on go
.PHONY: install
install:
$(GO) install -v -ldflags '$(LDFLAGS)' .
.PHONY: docker.build
docker.build:
docker build -t moul/sshportal .
.PHONY: integration
integration:
@@ -27,21 +25,23 @@ dev:
-$(GO) get github.com/githubnemo/CompileDaemon
CompileDaemon -exclude-dir=.git -exclude=".#*" -color=true -command="./sshportal server --debug --bind-address=:$(PORT) --aes-key=$(AES_KEY) $(EXTRA_RUN_OPTS)" .
.PHONY: test
test:
$(GO) test -i ./...
$(GO) test -v ./...
.PHONY: lint
lint:
golangci-lint run --verbose ./...
.PHONY: backup
backup:
mkdir -p data/backups
cp sshportal.db data/backups/$(shell date +%s)-$(VERSION)-sshportal.sqlite
cp sshportal.db data/backups/$(shell date +%s)-$(DB_VERSION)-sshportal.sqlite
doc:
dot -Tsvg ./.assets/overview.dot > ./.assets/overview.svg
dot -Tsvg ./.assets/cluster-mysql.dot > ./.assets/cluster-mysql.svg
dot -Tsvg ./.assets/flow-diagram.dot > ./.assets/flow-diagram.svg
dot -Tpng ./.assets/overview.dot > ./.assets/overview.png
dot -Tpng ./.assets/cluster-mysql.dot > ./.assets/cluster-mysql.png
dot -Tpng ./.assets/flow-diagram.dot > ./.assets/flow-diagram.png
.PHONY: goreleaser
goreleaser:
GORELEASER_GITHUB_TOKEN=$(GORELEASER_GITHUB_TOKEN) GITHUB_TOKEN=$(GITHUB_TOKEN) goreleaser --rm-dist
.PHONY: goreleaser-dry-run
goreleaser-dry-run:
goreleaser --snapshot --skip-publish --rm-dist

436
README.md
View File

@@ -1,65 +1,45 @@
# sshportal
[![CircleCI](https://circleci.com/gh/moul/sshportal.svg?style=svg)](https://circleci.com/gh/moul/sshportal)
[![Docker Build Status](https://img.shields.io/docker/build/moul/sshportal.svg)](https://hub.docker.com/r/moul/sshportal/)
[![Go Report Card](https://goreportcard.com/moul.io/sshportal)](https://goreportcard.com/report/moul.io/sshportal)
[![Go Report Card](https://goreportcard.com/badge/moul.io/sshportal)](https://goreportcard.com/report/moul.io/sshportal)
[![GoDoc](https://godoc.org/moul.io/sshportal?status.svg)](https://godoc.org/moul.io/sshportal)
[![License](https://img.shields.io/github/license/moul/sshportal.svg)](https://github.com/moul/sshportal/blob/master/LICENSE)
[![Financial Contributors on Open Collective](https://opencollective.com/sshportal/all/badge.svg?label=financial+contributors)](https://opencollective.com/sshportal) [![License](https://img.shields.io/github/license/moul/sshportal.svg)](https://github.com/moul/sshportal/blob/master/LICENSE)
[![GitHub release](https://img.shields.io/github/release/moul/sshportal.svg)](https://github.com/moul/sshportal/releases)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmoul%2Fsshportal.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmoul%2Fsshportal?ref=badge_shield)
<!-- temporarily broken? [![Docker Build Status](https://img.shields.io/docker/build/moul/sshportal.svg)](https://hub.docker.com/r/moul/sshportal/) -->
Jump host/Jump server without the jump, a.k.a Transparent SSH bastion
![sshportal demo](https://github.com/moul/sshportal/raw/master/.assets/demo.gif)
<img src="https://raw.githubusercontent.com/moul/sshportal/master/.assets/bastion.jpg" width="50%">
Features include: independence of users and hosts, convenient user invite system, connecting to servers that don't support SSH keys, various levels of access, and many more. Easy to install, run and configure.
![Flow Diagram](https://raw.githubusercontent.com/moul/sshportal/master/.assets/flow-diagram.png)
---
## Overview
## Contents
![sshportal overview](https://raw.github.com/moul/sshportal/master/.assets/overview.svg?sanitize=true)
<!-- toc -->
## Features
- [Installation and usage](#installation-and-usage)
- [Use cases](#use-cases)
- [Features and limitations](#features-and-limitations)
- [Docker](#docker)
- [Manual Install](#manual-install)
- [Backup / Restore](#backup--restore)
- [built-in shell](#built-in-shell)
- [Demo data](#demo-data)
- [Shell commands](#shell-commands)
- [Healthcheck](#healthcheck)
- [portal alias (.ssh/config)](#portal-alias-sshconfig)
- [Scaling](#scaling)
- [Under the hood](#under-the-hood)
* Single autonomous binary (~10-20Mb) with no runtime dependencies (embeds ssh server and client)
* Portable / Cross-platform (regularly tested on linux and OSX/darwin)
* Store data in [Sqlite3](https://www.sqlite.org/) or [MySQL](https://www.mysql.com) (probably easy to add postgres, mssql thanks to gorm)
* Stateless -> horizontally scalable when using [MySQL](https://www.mysql.com) as the backend
* Connect to remote host using key or password
* Admin commands can be run directly or in an interactive shell
* Host management
* User management (invite, group, stats)
* Host Key management (create, remove, update, import)
* Automatic remote host key learning
* User Key management (multile keys per user)
* ACL management (acl+user-groups+host-groups)
* User roles (admin, trusted, standard, ...)
* User invitations (no more "give me your public ssh key please")
* Easy server installation (generate shell command to setup `authorized_keys`)
* Sensitive data encryption
* Session management (see active connections, history, stats, stop)
* Audit log (logging every user action)
* Record TTY Session
* Tunnels logging
* Host Keys verifications shared across users
* Healthcheck user (replying OK to any user)
* SSH compatibility
* ipv4 and ipv6 support
* [`scp`](https://linux.die.net/man/1/scp) support
* [`rsync`](https://linux.die.net/man/1/rsync) support
* [tunneling](https://www.ssh.com/ssh/tunneling/example) (local forward, remote forward, dynamic forward) support
* [`sftp`](https://www.ssh.com/ssh/sftp/) support
* [`ssh-agent`](https://www.ssh.com/ssh/agent) support
* [`X11 forwarding`](http://en.tldp.org/HOWTO/XDMCP-HOWTO/ssh.html) support
* Git support (can be used to easily use multiple user keys on GitHub, or access your own firewalled gitlab server)
* Do not require any SSH client modification or custom `.ssh/config`, works with every tested SSH programming libraries and every tested SSH clients
* SSH to non-SSH proxy
* [Telnet](https://www.ssh.com/ssh/telnet) support
<!-- tocstop -->
## (Known) limitations
---
* Does not work (yet?) with [`mosh`](https://mosh.org/)
## Usage
## Installation and usage
Start the server
@@ -80,6 +60,8 @@ Shared connection to localhost closed.
$
```
If the association fails and you are promted for a password, verify that the host you're connecting from has a SSH key set up or generate one with ```ssh-keygen -t rsa```
Drop an interactive administrator shell
```console
@@ -139,15 +121,159 @@ To associate this account with a key, use the following SSH user: 'invite:NfHK5a
config>
```
## Flow Diagram
Demo gif:
![sshportal demo](https://github.com/moul/sshportal/raw/master/.assets/demo.gif)
![Flow Diagram](https://raw.github.com/moul/sshportal/master/.assets/flow-diagram.svg?sanitize=true)
---
## Use cases
Used by educators to provide temporary access to students. [Feedback from a teacher](https://github.com/moul/sshportal/issues/64). The author is using it in one of his projects, *pathwar*, to dynamically configure hosts and users, so that he can give temporary accesses for educational purposes.
*vptech*, the vente-privee.com technical team (a group of over 6000 people) is using it internally to manage access to servers/routers, saving hours on configuration management and not having to share the configuration information.
There are companies who use a jump host to monitor connections at a single point.
A hosting company is using SSHportal for its “logging” feature, among the others. As every session is logged and introspectable, they have a detailed history of who performed which action. This company made its own contribution on the project, allowing the support of [more than 65.000 sessions in the database](https://github.com/moul/sshportal/pull/76).
The project has also received [multiple contributions from a security researcher](https://github.com/moul/sshportal/pulls?q=is%3Apr+author%3Asabban+sort%3Aupdated-desc) that made a thesis on quantum cryptography. This person uses SSHportal in their security-hardened hosting company.
If you need to invite multiple people to an event (hackathon, course, etc), the day before the event you can create multiple accounts at once, print the invite, and distribute the paper.
---
## Features and limitations
* Single autonomous binary (~10-20Mb) with no runtime dependencies (embeds ssh server and client)
* Portable / Cross-platform (regularly tested on linux and OSX/darwin)
* Store data in [Sqlite3](https://www.sqlite.org/) or [MySQL](https://www.mysql.com) (probably easy to add postgres, mssql thanks to gorm)
* Stateless -> horizontally scalable when using [MySQL](https://www.mysql.com) as the backend
* Connect to remote host using key or password
* Admin commands can be run directly or in an interactive shell
* Host management
* User management (invite, group, stats)
* Host Key management (create, remove, update, import)
* Automatic remote host key learning
* User Key management (multile keys per user)
* ACL management (acl+user-groups+host-groups)
* User roles (admin, trusted, standard, ...)
* User invitations (no more "give me your public ssh key please")
* Easy server installation (generate shell command to setup `authorized_keys`)
* Sensitive data encryption
* Session management (see active connections, history, stats, stop)
* Audit log (logging every user action)
* Record TTY Session (with [ttyrec](https://en.wikipedia.org/wiki/Ttyrec) format, use `ttyplay` for replay)
* Tunnels logging
* Host Keys verifications shared across users
* Healthcheck user (replying OK to any user)
* SSH compatibility
* ipv4 and ipv6 support
* [`scp`](https://linux.die.net/man/1/scp) support
* [`rsync`](https://linux.die.net/man/1/rsync) support
* [tunneling](https://www.ssh.com/ssh/tunneling/example) (local forward, remote forward, dynamic forward) support
* [`sftp`](https://www.ssh.com/ssh/sftp/) support
* [`ssh-agent`](https://www.ssh.com/ssh/agent) support
* [`X11 forwarding`](http://en.tldp.org/HOWTO/XDMCP-HOWTO/ssh.html) support
* Git support (can be used to easily use multiple user keys on GitHub, or access your own firewalled gitlab server)
* Do not require any SSH client modification or custom `.ssh/config`, works with every tested SSH programming libraries and every tested SSH clients
* SSH to non-SSH proxy
* [Telnet](https://www.ssh.com/ssh/telnet) support
**(Known) limitations**
* Does not work (yet?) with [`mosh`](https://mosh.org/)
* It is not possible for a user to access a host with the same name as the user. This is easily circumvented by changing the user name, especially since the most common use cases does not expose it.
* It is not possible access a host named `healthcheck` as this is a built in command.
---
## Docker
Docker is the recommended way to run sshportal.
An [automated build is setup on the Docker Hub](https://hub.docker.com/r/moul/sshportal/tags/).
```console
# Start a server in background
# mount `pwd` to persist the sqlite database file
docker run -p 2222:2222 -d --name=sshportal -v "$(pwd):$(pwd)" -w "$(pwd)" moul/sshportal:v1.10.0
# check logs (mandatory on first run to get the administrator invite token)
docker logs -f sshportal
```
The easier way to upgrade sshportal is to do the following:
```sh
# we consider you were using an old version and you want to use the new version v1.10.0
# stop and rename the last working container + backup the database
docker stop sshportal
docker rename sshportal sshportal_old
cp sshportal.db sshportal.db.bkp
# run the new version
docker run -p 2222:2222 -d --name=sshportal -v "$(pwd):$(pwd)" -w "$(pwd)" moul/sshportal:v1.10.0
# check the logs for migration or cross-version incompabitility errors
docker logs -f sshportal
```
Now you can test ssh-ing to sshportal to check if everything looks OK.
In case of problem, you can rollback to the latest working version with the latest working backup, using:
```sh
docker stop sshportal
docker rm sshportal
cp sshportal.db.bkp sshportal.db
docker rename sshportal_old sshportal
docker start sshportal
docker logs -f sshportal
```
---
## Manual Install
Get the latest version using GO.
```sh
GO111MODULE=on go get -u moul.io/sshportal
```
---
## Backup / Restore
sshportal embeds built-in backup/restore methods which basically import/export JSON objects:
```sh
# Backup
ssh portal config backup > sshportal.bkp
# Restore
ssh portal config restore < sshportal.bkp
```
This method is particularly useful as it should be resistant against future DB schema changes (expected during development phase).
I suggest you to be careful during this development phase, and use an additional backup method, for example:
```sh
# sqlite dump
sqlite3 sshportal.db .dump > sshportal.sql.bkp
# or just the immortal cp
cp sshportal.db sshportal.db.bkp
```
---
## built-in shell
`sshportal` embeds a configuration CLI.
By default, the configuration user is `admin`, (can be changed using `--config-user=<value>` when starting the server.
By default, the configuration user is `admin`, (can be changed using `--config-user=<value>` when starting the server. The shell is also accessible through `ssh [username]@portal.example.org`.
Each commands can be run directly by using this syntax: `ssh admin@portal.example.org <command> [args]`:
@@ -157,7 +283,29 @@ ssh admin@portal.example.org host inspect toto
You can enter in interactive mode using this syntax: `ssh admin@portal.example.org`
### Synopsis
![sshportal overview](https://raw.github.com/moul/sshportal/master/.assets/overview.png)
---
## Demo data
The following servers are freely available, without external registration,
it makes it easier to quickly test `sshportal` without configuring your own servers to accept sshportal connections.
```
ssh portal host create new@sdf.org
ssh sdf@portal
ssh portal host create test@whoami.filippo.io
ssh whoami@portal
ssh portal host create test@chat.shazow.net
ssh chat@portal
```
---
## Shell commands
```sh
# acl management
@@ -180,11 +328,11 @@ event inspect [-h] EVENT...
# host management
host help
host create [-h] [--name=<value>] [--password=<value>] [--comment=<value>] [--key=KEY] [--group=HOSTGROUP...] [--hop=HOST] <username>[:<password>]@<host>[:<port>]
host create [-h] [--name=<value>] [--password=<value>] [--comment=<value>] [--key=KEY] [--group=HOSTGROUP...] [--hop=HOST] [--logging=MODE] <username>[:<password>]@<host>[:<port>]
host inspect [-h] [--decrypt] HOST...
host ls [-h] [--latest] [--quiet]
host rm [-h] HOST...
host update [-h] [--name=<value>] [--comment=<value>] [--key=KEY] [--assign-group=HOSTGROUP...] [--unassign-group=HOSTGROUP...] [--set-hop=HOST] [--unset-hop] HOST...
host update [-h] [--name=<value>] [--comment=<value>] [--key=KEY] [--assign-group=HOSTGROUP...] [--unassign-group=HOSTGROUP...] [--logging-MODE] [--set-hop=HOST] [--unset-hop] HOST...
# hostgroup management
hostgroup help
@@ -218,7 +366,7 @@ user update [-h] [--name=<value>] [--email=<value>] [--set-admin] [--unset-admin
# usergroup management
usergroup help
hostgroup create [-h] [--name=<value>] [--comment=<value>]
usergroup create [-h] [--name=<value>] [--comment=<value>]
usergroup inspect [-h] USERGROUP...
usergroup ls [-h] [--latest] [--quiet]
usergroup rm [-h] USERGROUP...
@@ -230,120 +378,7 @@ info [-h]
version [-h]
```
## Docker
Docker is the recommended way to run sshportal.
An [automated build is setup on the Docker Hub](https://hub.docker.com/r/moul/sshportal/tags/).
```console
# Start a server in background
# mount `pwd` to persist the sqlite database file
docker run -p 2222:2222 -d --name=sshportal -v "$(pwd):$(pwd)" -w "$(pwd)" moul/sshportal:v1.9.0
# check logs (mandatory on first run to get the administrator invite token)
docker logs -f sshportal
```
The easier way to upgrade sshportal is to do the following:
```sh
# we consider you were using an old version and you want to use the new version v1.9.0
# stop and rename the last working container + backup the database
docker stop sshportal
docker rename sshportal sshportal_old
cp sshportal.db sshportal.db.bkp
# run the new version
docker run -p 2222:2222 -d --name=sshportal -v "$(pwd):$(pwd)" -w "$(pwd)" moul/sshportal:v1.9.0
# check the logs for migration or cross-version incompabitility errors
docker logs -f sshportal
```
Now you can test ssh-ing to sshportal to check if everything looks OK.
In case of problem, you can rollback to the latest working version with the latest working backup, using:
```sh
docker stop sshportal
docker rm sshportal
cp sshportal.db.bkp sshportal.db
docker rename sshportal_old sshportal
docker start sshportal
docker logs -f sshportal
```
## Manual Install
Get the latest version using GO.
```sh
go get -u moul.io/sshportal
```
## portal alias (.ssh/config)
Edit your `~/.ssh/config` file (create it first if needed)
```ini
Host portal
User admin
Port 2222 # portal port
HostName 127.0.0.1 # portal hostname
```
```bash
# you can now run a shell using this:
ssh portal
# instead of this:
ssh localhost -p 2222 -l admin
# or connect to hosts using this:
ssh hostname@portal
# instead of this:
ssh localhost -p 2222 -l hostname
```
## Backup / Restore
sshportal embeds built-in backup/restore methods which basically import/export JSON objects:
```sh
# Backup
ssh portal config backup > sshportal.bkp
# Restore
ssh portal config restore < sshportal.bkp
```
This method is particularly useful as it should be resistant against future DB schema changes (expected during development phase).
I suggest you to be careful during this development phase, and use an additional backup method, for example:
```sh
# sqlite dump
sqlite3 sshportal.db .dump > sshportal.sql.bkp
# or just the immortal cp
cp sshportal.db sshportal.db.bkp
```
## Demo data
The following servers are freely available, without external registration,
it makes it easier to quickly test `sshportal` without configuring your own servers to accept sshportal connections.
```
ssh portal host create new@sdf.org
ssh sdf@portal
ssh portal host create test@whoami.filippo.io
ssh whoami@portal
ssh portal host create test@chat.shazow.net
ssh chat@portal
```
---
## Healthcheck
@@ -377,6 +412,33 @@ $ sshportal healthcheck --wait && ssh sshportal -l admin
config>
```
---
## portal alias (.ssh/config)
Edit your `~/.ssh/config` file (create it first if needed)
```ini
Host portal
User admin
Port 2222 # portal port
HostName 127.0.0.1 # portal hostname
```
```bash
# you can now run a shell using this:
ssh portal
# instead of this:
ssh localhost -p 2222 -l admin
# or connect to hosts using this:
ssh hostname@portal
# instead of this:
ssh localhost -p 2222 -l hostname
```
---
## Scaling
`sshportal` is stateless but relies on a database to store configuration and logs.
@@ -385,10 +447,12 @@ By default, `sshportal` uses a local [sqlite](https://www.sqlite.org/) database
You can run multiple instances of `sshportal` sharing a same [MySQL](https://www.mysql.com) database, using `sshportal --db-conn=user:pass@host/dbname?parseTime=true --db-driver=mysql`.
![sshportal cluster with MySQL backend](https://raw.github.com/moul/sshportal/master/.assets/cluster-mysql.svg?sanitize=true)
![sshportal cluster with MySQL backend](https://raw.github.com/moul/sshportal/master/.assets/cluster-mysql.png)
See [examples/mysql](http://github.com/moul/sshportal/tree/master/examples/mysql).
---
## Under the hood
* Docker first (used in dev, tests, by the CI and in production)
@@ -406,12 +470,38 @@ See [examples/mysql](http://github.com/moul/sshportal/tree/master/examples/mysql
* https://github.com/mgutz/ansi: Terminal color helpers
* https://github.com/urfave/cli: CLI flag parsing with subcommands support
![sshportal data model](https://raw.github.com/moul/sshportal/master/.assets/sql-schema.svg?sanitize=true)
![sshportal data model](https://raw.github.com/moul/sshportal/master/.assets/sql-schema.png)
## Note
## Contributors
This is totally experimental for now, so please file issues to let me know what you think about it!
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/moul/sshportal/graphs/contributors"><img src="https://opencollective.com/sshportal/contributors.svg?width=890&button=false" /></a>
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmoul%2Fsshportal.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmoul%2Fsshportal?ref=badge_large)
### Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/sshportal/contribute)]
#### Individuals
<a href="https://opencollective.com/sshportal"><img src="https://opencollective.com/sshportal/individuals.svg?width=890"></a>
#### Organizations
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/sshportal/contribute)]
<a href="https://opencollective.com/sshportal/organization/0/website"><img src="https://opencollective.com/sshportal/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/sshportal/organization/1/website"><img src="https://opencollective.com/sshportal/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/sshportal/organization/2/website"><img src="https://opencollective.com/sshportal/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/sshportal/organization/3/website"><img src="https://opencollective.com/sshportal/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/sshportal/organization/4/website"><img src="https://opencollective.com/sshportal/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/sshportal/organization/5/website"><img src="https://opencollective.com/sshportal/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/sshportal/organization/6/website"><img src="https://opencollective.com/sshportal/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/sshportal/organization/7/website"><img src="https://opencollective.com/sshportal/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/sshportal/organization/8/website"><img src="https://opencollective.com/sshportal/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/sshportal/organization/9/website"><img src="https://opencollective.com/sshportal/organization/9/avatar.svg"></a>
### Stargazers over time
[![Stargazers over time](https://starchart.cc/moul/sshportal.svg)](https://starchart.cc/moul/sshportal)

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-slate

40
acl.go
View File

@@ -1,40 +0,0 @@
package main
import "sort"
type ByWeight []*ACL
func (a ByWeight) Len() int { return len(a) }
func (a ByWeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByWeight) Less(i, j int) bool { return a[i].Weight < a[j].Weight }
func CheckACLs(user User, host Host) (string, error) {
// shared ACLs between user and host
aclMap := map[uint]*ACL{}
for _, userGroup := range user.Groups {
for _, userGroupACL := range userGroup.ACLs {
for _, hostGroup := range host.Groups {
for _, hostGroupACL := range hostGroup.ACLs {
if userGroupACL.ID == hostGroupACL.ID {
aclMap[userGroupACL.ID] = userGroupACL
}
}
}
}
}
// FIXME: add ACLs that match host pattern
// deny by default if no shared ACL
if len(aclMap) == 0 {
return string(ACLActionDeny), nil // default action
}
// transform map to slice and sort it
acls := make([]*ACL, 0, len(aclMap))
for _, acl := range aclMap {
acls = append(acls, acl)
}
sort.Sort(ByWeight(acls))
return acls[0].Action, nil
}

View File

@@ -1,47 +0,0 @@
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/jinzhu/gorm"
. "github.com/smartystreets/goconvey/convey"
)
func TestCheckACLs(t *testing.T) {
Convey("Testing CheckACLs", t, func() {
// create tmp dir
tempDir, err := ioutil.TempDir("", "sshportal")
So(err, ShouldBeNil)
defer func() {
So(os.RemoveAll(tempDir), ShouldBeNil)
}()
// create sqlite db
db, err := gorm.Open("sqlite3", filepath.Join(tempDir, "sshportal.db"))
So(err, ShouldBeNil)
db.LogMode(false)
So(dbInit(db), ShouldBeNil)
// create dummy objects
var hostGroup HostGroup
err = HostGroupsByIdentifiers(db, []string{"default"}).First(&hostGroup).Error
So(err, ShouldBeNil)
db.Create(&Host{Groups: []*HostGroup{&hostGroup}})
//. load db
var (
hosts []Host
users []User
)
db.Preload("Groups").Preload("Groups.ACLs").Find(&hosts)
db.Preload("Groups").Preload("Groups.ACLs").Find(&users)
// test
action, err := CheckACLs(users[0], hosts[0])
So(err, ShouldBeNil)
So(action, ShouldEqual, ACLActionAllow)
})
}

View File

@@ -1,52 +0,0 @@
package main
import (
"fmt"
"os"
"time"
"github.com/urfave/cli"
)
type configServe struct {
aesKey string
dbDriver, dbURL string
logsLocation string
bindAddr string
debug, demo bool
idleTimeout time.Duration
}
func parseServeConfig(c *cli.Context) (*configServe, error) {
ret := &configServe{
aesKey: c.String("aes-key"),
dbDriver: c.String("db-driver"),
dbURL: c.String("db-conn"),
bindAddr: c.String("bind-address"),
debug: c.Bool("debug"),
demo: c.Bool("demo"),
logsLocation: c.String("logs-location"),
idleTimeout: c.Duration("idle-timeout"),
}
switch len(ret.aesKey) {
case 0, 16, 24, 32:
default:
return nil, fmt.Errorf("invalid aes key size, should be 16 or 24, 32")
}
return ret, nil
}
func ensureLogDirectory(location string) error {
// check for the logdir existence
logsLocation, err := os.Stat(location)
if err != nil {
if os.IsNotExist(err) {
return os.MkdirAll(location, os.ModeDir|os.FileMode(0750))
}
return err
}
if !logsLocation.IsDir() {
return fmt.Errorf("log directory cannot be created")
}
return nil
}

128
depaware.txt Normal file
View File

@@ -0,0 +1,128 @@
moul.io/sshportal dependencies: (generated by github.com/tailscale/depaware)
github.com/anmitsu/go-shlex from github.com/gliderlabs/ssh+
github.com/asaskevich/govalidator from moul.io/sshportal/pkg/bastion+
github.com/cpuguy83/go-md2man/v2/md2man from github.com/urfave/cli
LD 💣 github.com/creack/pty from github.com/kr/pty
github.com/docker/docker/pkg/namesgenerator from moul.io/sshportal/pkg/bastion
github.com/docker/docker/pkg/random from github.com/docker/docker/pkg/namesgenerator
github.com/dustin/go-humanize from moul.io/sshportal/pkg/bastion
github.com/gliderlabs/ssh from moul.io/sshportal+
github.com/go-sql-driver/mysql from github.com/jinzhu/gorm/dialects/mysql+
github.com/jinzhu/gorm from gopkg.in/gormigrate.v1+
github.com/jinzhu/gorm/dialects/mysql from moul.io/sshportal
github.com/jinzhu/gorm/dialects/postgres from moul.io/sshportal
github.com/jinzhu/gorm/dialects/sqlite from moul.io/sshportal
github.com/jinzhu/inflection from github.com/jinzhu/gorm
LD github.com/kr/pty from moul.io/sshportal
github.com/lib/pq from github.com/jinzhu/gorm/dialects/postgres
github.com/lib/pq/hstore from github.com/jinzhu/gorm/dialects/postgres
github.com/lib/pq/oid from github.com/lib/pq
github.com/lib/pq/scram from github.com/lib/pq
💣 github.com/mattn/go-colorable from github.com/mgutz/ansi
💣 github.com/mattn/go-isatty from github.com/mattn/go-colorable
github.com/mattn/go-runewidth from github.com/olekukonko/tablewriter
💣 github.com/mattn/go-sqlite3 from github.com/jinzhu/gorm/dialects/sqlite
github.com/mgutz/ansi from moul.io/sshportal/pkg/bastion
github.com/olekukonko/tablewriter from moul.io/sshportal/pkg/bastion
github.com/pkg/errors from moul.io/sshportal/pkg/bastion
github.com/reiver/go-oi from github.com/reiver/go-telnet+
github.com/reiver/go-telnet from moul.io/sshportal/pkg/bastion
github.com/russross/blackfriday/v2 from github.com/cpuguy83/go-md2man/v2/md2man
github.com/sabban/bastion/pkg/logchannel from moul.io/sshportal/pkg/bastion
github.com/shurcooL/sanitized_anchor_name from github.com/russross/blackfriday/v2
github.com/urfave/cli from moul.io/sshportal+
gopkg.in/gormigrate.v1 from moul.io/sshportal/pkg/bastion
moul.io/srand from moul.io/sshportal
moul.io/sshportal/pkg/bastion from moul.io/sshportal
moul.io/sshportal/pkg/crypto from moul.io/sshportal/pkg/bastion
moul.io/sshportal/pkg/dbmodels from moul.io/sshportal/pkg/bastion+
golang.org/x/crypto/blowfish from golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/chacha20poly1305 from crypto/tls
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
golang.org/x/crypto/curve25519 from crypto/tls+
golang.org/x/crypto/ed25519 from golang.org/x/crypto/ssh
golang.org/x/crypto/hkdf from crypto/tls
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/ssh from github.com/gliderlabs/ssh+
golang.org/x/crypto/ssh/terminal from moul.io/sshportal/pkg/bastion
golang.org/x/net/dns/dnsmessage from net
D golang.org/x/net/route from net
golang.org/x/sys/cpu from golang.org/x/crypto/chacha20poly1305
LD golang.org/x/sys/unix from github.com/mattn/go-isatty+
W golang.org/x/sys/windows from golang.org/x/crypto/ssh/terminal
bufio from crypto/rand+
bytes from bufio+
container/list from crypto/tls
context from crypto/tls+
crypto from crypto/ecdsa+
crypto/aes from crypto/ecdsa+
crypto/cipher from crypto/aes+
crypto/des from crypto/tls+
crypto/dsa from crypto/x509+
crypto/ecdsa from crypto/tls+
crypto/ed25519 from crypto/tls+
crypto/elliptic from crypto/ecdsa+
crypto/hmac from crypto/tls+
crypto/md5 from crypto/tls+
crypto/rand from crypto/ed25519+
crypto/rc4 from crypto/tls+
crypto/rsa from crypto/tls+
crypto/sha1 from crypto/tls+
crypto/sha256 from crypto/tls+
crypto/sha512 from crypto/ecdsa+
crypto/subtle from crypto/aes+
crypto/tls from github.com/go-sql-driver/mysql+
crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509
database/sql from github.com/go-sql-driver/mysql+
database/sql/driver from database/sql+
encoding from encoding/json
encoding/asn1 from crypto/x509+
encoding/base64 from encoding/json+
encoding/binary from crypto/aes+
encoding/csv from github.com/olekukonko/tablewriter
encoding/hex from crypto/x509+
encoding/json from github.com/asaskevich/govalidator+
encoding/pem from crypto/tls+
errors from bufio+
flag from github.com/urfave/cli
fmt from crypto/tls+
go/ast from github.com/jinzhu/gorm
go/scanner from go/ast
go/token from go/ast+
hash from crypto+
html from github.com/asaskevich/govalidator+
io from bufio+
io/fs from crypto/rand+
io/ioutil from crypto/x509+
log from github.com/gliderlabs/ssh+
math from crypto/rsa+
math/big from crypto/dsa+
math/bits from crypto/md5+
math/rand from github.com/docker/docker/pkg/random+
net from crypto/tls+
net/url from crypto/x509+
os from crypto/rand+
LD os/exec from github.com/creack/pty+
os/user from github.com/lib/pq+
path from github.com/asaskevich/govalidator+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from github.com/asaskevich/govalidator+
regexp/syntax from regexp
sort from database/sql+
strconv from crypto+
strings from bufio+
sync from context+
sync/atomic from context+
syscall from crypto/rand+
text/tabwriter from github.com/urfave/cli
text/template from github.com/urfave/cli
text/template/parse from text/template
time from context+
unicode from bytes+
unicode/utf16 from encoding/asn1+
unicode/utf8 from bufio+

View File

@@ -5,3 +5,6 @@ run:
docker-compose exec sshportal /bin/sshportal healthcheck --wait --quiet
docker-compose run client /integration/_client.sh
docker-compose down
build:
docker-compose build

View File

@@ -28,7 +28,7 @@ ssh sshportal -l invite:integration
ssh sshportal -l admin hostgroup create
ssh sshportal -l admin hostgroup create --name=hg1
ssh sshportal -l admin hostgroup create --name=hg2 --comment=test
ssh sshportal -l admin usergroup inspect hg1 hg2
ssh sshportal -l admin hostgroup inspect hg1 hg2
ssh sshportal -l admin hostgroup ls
ssh sshportal -l admin usergroup create

View File

@@ -4,12 +4,16 @@ services:
sshportal:
build: ../..
restart: unless-stopped
environment:
SSHPORTAL_DB_DRIVER: mysql
SSHPORTAL_DATABASE_URL: "root:root@tcp(mysql:3306)/db?charset=utf8&parseTime=true&loc=Local"
SSHPORTAL_DEBUG: 1
depends_on:
mysql:
condition: service_healthy
links:
- mysql
command: server --db-driver=mysql --debug --db-conn="root:root@tcp(mysql:3306)/db?charset=utf8&parseTime=true&loc=Local"
command: server
ports:
- 2222:2222

66
go.mod generated
View File

@@ -1,43 +1,37 @@
module moul.io/sshportal
require (
cloud.google.com/go v0.33.1 // indirect
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/creack/pty v1.1.11 // indirect
github.com/docker/docker v20.10.6+incompatible
github.com/dustin/go-humanize v1.0.0
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/gliderlabs/ssh v0.1.1 // indirect
github.com/go-gormigrate/gormigrate v1.2.1
github.com/go-sql-driver/mysql v1.4.1 // indirect
github.com/google/go-cmp v0.2.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/jinzhu/gorm v1.9.1
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
github.com/jinzhu/now v0.0.0-20181116074157-8ec929ed50c3 // indirect
github.com/joho/godotenv v1.3.0 // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/kr/pty v1.1.3
github.com/lib/pq v1.0.0 // indirect
github.com/mattn/go-colorable v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.4 // indirect
github.com/mattn/go-runewidth v0.0.3 // indirect
github.com/mattn/go-sqlite3 v1.10.0 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
github.com/moby/moby v0.0.0-20171102073902-76531ccdeb58
github.com/moul/ssh v0.1.1-0.20181116135657-8b3cdd49b6d2
github.com/olekukonko/tablewriter v0.0.1
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/reiver/go-oi v0.0.0-20160325061615-431c83978379
github.com/gliderlabs/ssh v0.3.2
github.com/go-sql-driver/mysql v1.6.0
github.com/jinzhu/gorm v1.9.16
github.com/kr/pty v1.1.8
github.com/lib/pq v1.10.1 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-runewidth v0.0.12 // indirect
github.com/mattn/go-sqlite3 v1.14.7 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1
github.com/reiver/go-oi v1.0.0
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sabban/bastion v0.0.0-20180110125408-b9d3c9b1f4d3
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c
github.com/urfave/cli v0.0.0-20171031025534-7f4b273a0585
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 // indirect
google.golang.org/appengine v1.3.0 // indirect
gopkg.in/stretchr/testify.v1 v1.2.2 // indirect
github.com/smartystreets/goconvey v1.6.4
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
github.com/urfave/cli v1.22.5
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed // indirect
golang.org/x/tools v0.1.0
gopkg.in/gormigrate.v1 v1.6.0
moul.io/srand v1.6.1
)
go 1.14

203
go.sum generated
View File

@@ -1,85 +1,172 @@
cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk=
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/docker/docker v20.10.6+incompatible h1:oXI3Vas8TI8Eu/EjH4srKHJBVqraSzJybhxY7Om9faQ=
github.com/docker/docker v20.10.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-gormigrate/gormigrate v1.2.1 h1:y3jmLDVVxVkuIR4CR5Qu+lLiUUOtpGt+4zjkLH53Bls=
github.com/go-gormigrate/gormigrate v1.2.1/go.mod h1:EmaYTk8H9TxcUD9nFzNPaHlDUCePc1EstS+HTwcGNhE=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/gliderlabs/ssh v0.3.2 h1:gcfd1Aj/9RQxvygu4l3sak711f/5+VOwBw9C/7+N4EI=
github.com/gliderlabs/ssh v0.3.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jinzhu/gorm v1.9.1 h1:lDSDtsCt5AGGSKTs8AHlSDbbgif4G4+CKJ8ETBDVHTA=
github.com/jinzhu/gorm v1.9.1/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v0.0.0-20181116074157-8ec929ed50c3 h1:xvj06l8iSwiWpYgm8MbPp+naBg+pwfqmdXabzqPCn/8=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v0.0.0-20181116074157-8ec929ed50c3/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pty v1.1.3 h1:/Um6a/ZmD5tF7peoOJ5oN5KMQ0DrGVQSXLNwyckutPk=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-colorable v0.0.0-20171111065953-6fcc0c1fd9b6 h1:G4Z3Qt5LMB7t8O2mvgRGe5Napynl/AXz+kEPvYXaggQ=
github.com/mattn/go-colorable v0.0.0-20171111065953-6fcc0c1fd9b6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.1 h1:6VXZrLU0jHBYyAqrSPa+MgPfnSvTPuMgK+k0o5kVFWo=
github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/moby/moby v0.0.0-20171102073902-76531ccdeb58 h1:ce/WsOd8CTi+SX+mtZolkjdHRFh4WSqqV9pnedmqY1w=
github.com/moby/moby v0.0.0-20171102073902-76531ccdeb58/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
github.com/moul/ssh v0.1.1-0.20181116134500-51417a721208 h1:Y97oa5mCq1XZ+46noGJySDjs6Kf8iY0FqfEa4wPutdc=
github.com/moul/ssh v0.1.1-0.20181116134500-51417a721208/go.mod h1:7g1Z1WW1l5W9MgjgsE6ehNzvjmA8qe9kJ/G8kdanYEg=
github.com/moul/ssh v0.1.1-0.20181116135657-8b3cdd49b6d2 h1:IAH3/wuCKXdfGf4zrH2PtTnp0PhWtL+Cld840EfLQ5o=
github.com/moul/ssh v0.1.1-0.20181116135657-8b3cdd49b6d2/go.mod h1:7g1Z1WW1l5W9MgjgsE6ehNzvjmA8qe9kJ/G8kdanYEg=
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 h1:+/+DxvQaYifJ+grD4klzrS5y+KJXldn/2YTl5JG+vZ8=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/reiver/go-oi v0.0.0-20160325061615-431c83978379 h1:NBPkf14RzPYmr3478XQcmQyMKkxSvguL7+cyKKNvGxY=
github.com/reiver/go-oi v0.0.0-20160325061615-431c83978379/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI=
github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM=
github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI=
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas=
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e/go.mod h1:+5vNVvEWwEIx86DB9Ke/+a5wBI464eDRo3eF0LcfpWg=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sabban/bastion v0.0.0-20180110125408-b9d3c9b1f4d3 h1:yxUGvEatvDMO6gkhwx82Va+Czdyui9LiCw6a5YB/2f8=
github.com/sabban/bastion v0.0.0-20180110125408-b9d3c9b1f4d3/go.mod h1:1Q04m7wmv/IMoZU9t8UkH+n9McWn4i3H9v9LnMgqloo=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/urfave/cli v0.0.0-20171031025534-7f4b273a0585 h1:fKnLpe72GC+2GbMpMp0AmcqVvJGW5GBaWD5C2gomMEg=
github.com/urfave/cli v0.0.0-20171031025534-7f4b273a0585/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBWY6aGilF+IRlQIdmhzLrsEmF6JgN+Ryw=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 h1:YoY1wS6JYVRpIfFngRf2HHo9R9dAne3xbkGOQ5rJXjU=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 h1:1Bs6RVeBFtLZ8Yi1Hk07DiOqzvwLD/4hln4iahvFlag=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M=
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
moul.io/srand v1.6.1 h1:SJ335F+54ivLdlH7wH52Rtyv0Ffos6DpsF5wu3ZVMXU=
moul.io/srand v1.6.1/go.mod h1:P2uaZB+GFstFNo8sEj6/U8FRV1n25kD0LLckFpJ+qvc=

View File

@@ -0,0 +1,22 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

21
helm/sshportal/Chart.yaml Normal file
View File

@@ -0,0 +1,21 @@
apiVersion: v2
name: sshportal
description: A Helm chart for SSHPortal on Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application.
appVersion: 1.10.0

View File

@@ -0,0 +1,33 @@
1. Get the admin invitation token (only on first install):
export INVITE=$(kubectl --namespace sshportal logs -l "app.kubernetes.io/name={{ include "sshportal.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" | grep -Eo "invite:[a-zA-Z0-9]+")
2. Get the service IP and Port:
{{- if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "sshportal.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "sshportal.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "sshportal.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "sshportal.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 2222:{{ .Values.service.port }}
{{- end }}
3. Enroll your SSH public key:
{{- if contains "NodePort" .Values.service.type }}
ssh $NODE_IP -p $NODE_PORT -l $INVITE
{{- else if contains "LoadBalancer" .Values.service.type }}
ssh $SERVICE_IP -p {{ .Values.service.port }} -l $INVITE
{{- else if contains "ClusterIP" .Values.service.type }}
ssh localhost -p 2222 -l $INVITE
{{- end }}
4. Configure your {{ include "sshportal.name" . }} install:
{{- if contains "NodePort" .Values.service.type }}
ssh admin@$NODE_IP -p $NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
ssh admin@$SERVICE_IP -p {{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
ssh admin@localhost -p 2222
{{- end }}

View File

@@ -0,0 +1,63 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "sshportal.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "sshportal.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "sshportal.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "sshportal.labels" -}}
helm.sh/chart: {{ include "sshportal.chart" . }}
{{ include "sshportal.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{/*
Selector labels
*/}}
{{- define "sshportal.selectorLabels" -}}
app.kubernetes.io/name: {{ include "sshportal.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{/*
Create the name of the service account to use
*/}}
{{- define "sshportal.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "sshportal.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

View File

@@ -0,0 +1,69 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "sshportal.fullname" . }}
labels:
{{- include "sshportal.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "sshportal.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "sshportal.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:v{{ .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: ssh
containerPort: 2222
protocol: TCP
livenessProbe:
exec:
command:
- sshportal
- healthcheck
- --quiet
readinessProbe:
exec:
command:
- sshportal
- healthcheck
- --quiet
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
{{- if .Values.mysql.enabled }}
- name: SSHPORTAL_DATABASE_URL
value: {{ .Values.mysql.user }}:{{ .Values.mysql.password }}@tcp({{ .Values.mysql.server }}:{{ .Values.mysql.port }})/{{ .Values.mysql.database }}?charset=utf8&parseTime=true&loc=Local
- name: SSHPORTAL_DB_DRIVER
value: mysql
{{- end }}
{{- if .Values.debug}}
- name: SSHPORTAL_DEBUG
value: "1"
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,21 @@
{{- if .Values.mysql.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "sshportal.fullname" . }}
labels:
{{- include "sshportal.labels" . | nindent 4 }}
spec:
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "sshportal.fullname" . }}
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.cpuTarget }}
{{- end }}

View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "sshportal.fullname" . }}
annotations:
{{- toYaml .Values.service.annotations | nindent 4 }}
labels:
{{- include "sshportal.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: 2222
protocol: TCP
name: ssh
selector:
{{- include "sshportal.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "sshportal.fullname" . }}-test-connection"
labels:
{{ include "sshportal.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test-success
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "sshportal.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

119
helm/sshportal/values.yaml Normal file
View File

@@ -0,0 +1,119 @@
# Default values for sshportal.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
## Enable SSHPortal debug mode
##
debug: false
## SSH Portal Docker image
##
image:
repository: moul/sshportal
pullPolicy: IfNotPresent
## Reference to one or more secrets to be used when pulling images
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
##
imagePullSecrets: []
## Provide a name in place of sshportal for `app:` labels
##
nameOverride: ""
## Provide a name to substitute for the full names of resources
##
fullnameOverride: ""
## PodSecurityContext holds pod-level security attributes.
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
##
podSecurityContext: {}
# fsGroup: 2000
## SecurityContext holds container-level security attributes.
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
##
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
## Service
##
service:
## Configure additional annotations for SSHPortal service
##
annotations: {}
# service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
## Service type, one of
## NodePort, ClusterIP, LoadBalancer
##
type: LoadBalancer
## Port to expose on the service
##
port: 22
## Define resources requests and limits
## ref: https://kubernetes.io/docs/user-guide/compute-resources/
##
resources: {}
# requests:
# cpu: 100m
# memory: 128Mi
# limits:
# cpu: 2
# memory: 2Gi
## Mysql/MariaDB configuration for HA
##
mysql:
enabled: false
## Database user
##
user: sshportal
## Database password
##
password: change_me
## Database name
##
database: sshportal
## Database server FQDN or IP
##
server: mariadb-mariadb-galera
## Database port
##
port: 3306
## Define which Nodes the Pods are scheduled on.
## ref: https://kubernetes.io/docs/user-guide/node-selection/
##
nodeSelector: {}
## The pod's tolerations.
## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
##
tolerations: []
## Assign custom affinity rules
## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
##
affinity: {}
## HPA support, require `mysql.enable: true`
## This section enables sshportal to autoscale based on metrics.
##
autoscaling:
maxReplicas: 4
minReplicas: 2
cpuTarget: 60

11
internal/tools/tools.go Normal file
View File

@@ -0,0 +1,11 @@
// +build tools
package tools
import (
// required by depaware
_ "github.com/tailscale/depaware/depaware"
// required by goimports
_ "golang.org/x/tools/cover"
)

139
main.go
View File

@@ -1,41 +1,33 @@
package main // import "moul.io/sshportal"
import (
"fmt"
"log"
"math"
"math/rand"
"net"
"os"
"path"
"time"
"github.com/jinzhu/gorm"
_ "github.com/go-sql-driver/mysql"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/moul/ssh"
"github.com/urfave/cli"
gossh "golang.org/x/crypto/ssh"
"moul.io/srand"
)
var (
// Version should be updated by hand at each release
Version = "1.9.0"
// GitTag will be overwritten automatically by the build system
GitTag string
GitTag = "n/a"
// GitSha will be overwritten automatically by the build system
GitSha string
// GitBranch will be overwritten automatically by the build system
GitBranch string
GitSha = "n/a"
)
func main() {
rand.Seed(time.Now().UnixNano())
rand.Seed(srand.MustSecure())
app := cli.NewApp()
app.Name = path.Base(os.Args[0])
app.Author = "Manfred Touron"
app.Version = Version + " (" + GitSha + ")"
app.Version = GitTag + " (" + GitSha + ")"
app.Email = "https://moul.io/sshportal"
app.Commands = []cli.Command{
{
@@ -45,7 +37,7 @@ func main() {
if err := ensureLogDirectory(c.String("logs-location")); err != nil {
return err
}
cfg, err := parseServeConfig(c)
cfg, err := parseServerConfig(c)
if err != nil {
return err
}
@@ -59,33 +51,43 @@ func main() {
Usage: "SSH server bind address",
},
cli.StringFlag{
Name: "db-driver",
Value: "sqlite3",
Usage: "GORM driver (sqlite3)",
Name: "db-driver",
EnvVar: "SSHPORTAL_DB_DRIVER",
Value: "sqlite3",
Usage: "GORM driver (sqlite3)",
},
cli.StringFlag{
Name: "db-conn",
Value: "./sshportal.db",
Usage: "GORM connection string",
Name: "db-conn",
EnvVar: "SSHPORTAL_DATABASE_URL",
Value: "./sshportal.db",
Usage: "GORM connection string",
},
cli.BoolFlag{
Name: "debug, D",
Usage: "Display debug information",
Name: "debug, D",
EnvVar: "SSHPORTAL_DEBUG",
Usage: "Display debug information",
},
cli.StringFlag{
Name: "aes-key",
Usage: "Encrypt sensitive data in database (length: 16, 24 or 32)",
Name: "aes-key",
EnvVar: "SSHPORTAL_AES_KEY",
Usage: "Encrypt sensitive data in database (length: 16, 24 or 32)",
},
cli.StringFlag{
Name: "logs-location",
Value: "./log",
Usage: "Store user session files",
Name: "logs-location",
EnvVar: "SSHPORTAL_LOGS_LOCATION",
Value: "./log",
Usage: "Store user session files",
},
cli.DurationFlag{
Name: "idle-timeout",
Value: 0,
Usage: "Duration before an inactive connection is timed out (0 to disable)",
},
cli.StringFlag{
Name: "acl-check-cmd",
EnvVar: "SSHPORTAL_ACL_CHECK_CMD",
Usage: "Execute external command to check ACL",
},
},
}, {
Name: "healthcheck",
@@ -115,82 +117,3 @@ func main() {
log.Fatalf("error: %v", err)
}
}
var defaultChannelHandler ssh.ChannelHandler
func server(c *configServe) (err error) {
var db = (*gorm.DB)(nil)
// try to setup the local DB
if db, err = gorm.Open(c.dbDriver, c.dbURL); err != nil {
return
}
defer func() {
origErr := err
err = db.Close()
if origErr != nil {
err = origErr
}
}()
if err = db.DB().Ping(); err != nil {
return
}
db.LogMode(c.debug)
if err = dbInit(db); err != nil {
return
}
// create TCP listening socket
ln, err := net.Listen("tcp", c.bindAddr)
if err != nil {
return err
}
// configure server
srv := &ssh.Server{
Addr: c.bindAddr,
Handler: shellHandler, // ssh.Server.Handler is the handler for the DefaultSessionHandler
Version: fmt.Sprintf("sshportal-%s", Version),
}
// configure channel handler
defaultSessionHandler := srv.GetChannelHandler("session")
defaultDirectTcpipHandler := srv.GetChannelHandler("direct-tcpip")
defaultChannelHandler = func(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
switch newChan.ChannelType() {
case "session":
go defaultSessionHandler(srv, conn, newChan, ctx)
case "direct-tcpip":
go defaultDirectTcpipHandler(srv, conn, newChan, ctx)
default:
if err := newChan.Reject(gossh.UnknownChannelType, "unsupported channel type"); err != nil {
log.Printf("failed to reject chan: %v", err)
}
}
}
srv.SetChannelHandler("session", nil)
srv.SetChannelHandler("direct-tcpip", nil)
srv.SetChannelHandler("default", channelHandler)
if c.idleTimeout != 0 {
srv.IdleTimeout = c.idleTimeout
// gliderlabs/ssh requires MaxTimeout to be non-zero if we want to use IdleTimeout.
// So, set it to the max value, because we don't want a max timeout.
srv.MaxTimeout = math.MaxInt64
}
for _, opt := range []ssh.Option{
// custom PublicKeyAuth handler
ssh.PublicKeyAuth(publicKeyAuthHandler(db, c)),
ssh.PasswordAuth(passwordAuthHandler(db, c)),
// retrieve sshportal SSH private key from database
privateKeyFromDB(db, c.aesKey),
} {
if err := srv.SetOption(opt); err != nil {
return err
}
}
log.Printf("info: SSH Server accepting connections on %s, idle-timout=%v", c.bindAddr, c.idleTimeout)
return srv.Serve(ln)
}

120
pkg/bastion/acl.go Normal file
View File

@@ -0,0 +1,120 @@
package bastion
import (
"context"
"encoding/json"
"fmt"
"log"
"os/exec"
"sort"
"strings"
"time"
"moul.io/sshportal/pkg/dbmodels"
)
// ACLHookTimeout is timeout for external ACL hook execution
const ACLHookTimeout = 2 * time.Second
type byWeight []*dbmodels.ACL
func (a byWeight) Len() int { return len(a) }
func (a byWeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byWeight) Less(i, j int) bool { return a[i].Weight < a[j].Weight }
func checkACLs(user dbmodels.User, host dbmodels.Host, aclCheckCmd string) string {
currentTime := time.Now()
// shared ACLs between user and host
aclMap := map[uint]*dbmodels.ACL{}
for _, userGroup := range user.Groups {
for _, userGroupACL := range userGroup.ACLs {
for _, hostGroup := range host.Groups {
for _, hostGroupACL := range hostGroup.ACLs {
if userGroupACL.ID == hostGroupACL.ID {
if (userGroupACL.Inception == nil || currentTime.After(*userGroupACL.Inception)) &&
(userGroupACL.Expiration == nil || currentTime.Before(*userGroupACL.Expiration)) {
aclMap[userGroupACL.ID] = userGroupACL
}
}
}
}
}
}
// FIXME: add ACLs that match host pattern
// if no shared ACL then execute ACLs hook if it exists and return its result
if len(aclMap) == 0 {
action, err := checkACLsHook(aclCheckCmd, string(dbmodels.ACLActionDeny), user, host)
if err != nil {
log.Println(err)
}
return action
}
// transform map to slice and sort it
acls := make([]*dbmodels.ACL, 0, len(aclMap))
for _, acl := range aclMap {
acls = append(acls, acl)
}
sort.Sort(byWeight(acls))
action, err := checkACLsHook(aclCheckCmd, acls[0].Action, user, host)
if err != nil {
log.Println(err)
}
return action
}
// checkACLsHook executes external command to check ACL and passes following parameters:
// $1 - SSH Portal `action` (`allow` or `deny`)
// $2 - User as JSON string
// $3 - Host as JSON string
// External program has to return `allow` or `deny` in stdout.
// In case of any error function returns `action`.
func checkACLsHook(aclCheckCmd string, action string, user dbmodels.User, host dbmodels.Host) (string, error) {
if aclCheckCmd == "" {
return action, nil
}
ctx, cancel := context.WithTimeout(context.Background(), ACLHookTimeout)
defer cancel()
jsonUser, err := json.Marshal(user)
if err != nil {
return action, err
}
jsonHost, err := json.Marshal(host)
if err != nil {
return action, err
}
args := []string{
action,
string(jsonUser),
string(jsonHost),
}
cmd := exec.CommandContext(ctx, aclCheckCmd, args...)
out, err := cmd.Output()
if err != nil {
return action, err
}
if ctx.Err() == context.DeadlineExceeded {
return action, fmt.Errorf("external ACL hook command timed out")
}
outStr := strings.TrimSuffix(string(out), "\n")
switch outStr {
case string(dbmodels.ACLActionAllow):
return string(dbmodels.ACLActionAllow), nil
case string(dbmodels.ACLActionDeny):
return string(dbmodels.ACLActionDeny), nil
default:
return action, fmt.Errorf("acl-check-cmd wrong output '%s'", outStr)
}
}

49
pkg/bastion/acl_test.go Normal file
View File

@@ -0,0 +1,49 @@
package bastion // import "moul.io/sshportal/pkg/bastion"
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
. "github.com/smartystreets/goconvey/convey"
"moul.io/sshportal/pkg/dbmodels"
)
func TestCheckACLs(t *testing.T) {
Convey("Testing CheckACLs", t, func(c C) {
// create tmp dir
tempDir, err := ioutil.TempDir("", "sshportal")
c.So(err, ShouldBeNil)
defer func() {
c.So(os.RemoveAll(tempDir), ShouldBeNil)
}()
// create sqlite db
db, err := gorm.Open("sqlite3", filepath.Join(tempDir, "sshportal.db"))
c.So(err, ShouldBeNil)
db.LogMode(false)
c.So(DBInit(db), ShouldBeNil)
// create dummy objects
var hostGroup dbmodels.HostGroup
err = dbmodels.HostGroupsByIdentifiers(db, []string{"default"}).First(&hostGroup).Error
c.So(err, ShouldBeNil)
db.Create(&dbmodels.Host{Groups: []*dbmodels.HostGroup{&hostGroup}})
//. load db
var (
hosts []dbmodels.Host
users []dbmodels.User
)
db.Preload("Groups").Preload("Groups.ACLs").Find(&hosts)
db.Preload("Groups").Preload("Groups.ACLs").Find(&users)
// test
action := checkACLs(users[0], hosts[0], "")
c.So(action, ShouldEqual, dbmodels.ACLActionAllow)
})
}

View File

@@ -1,20 +1,23 @@
package main
package bastion // import "moul.io/sshportal/pkg/bastion"
import (
"fmt"
"io/ioutil"
"log"
"math/rand"
"os"
"os/user"
"strings"
"time"
"github.com/go-gormigrate/gormigrate"
"github.com/jinzhu/gorm"
gossh "golang.org/x/crypto/ssh"
gormigrate "gopkg.in/gormigrate.v1"
"moul.io/sshportal/pkg/crypto"
"moul.io/sshportal/pkg/dbmodels"
)
func dbInit(db *gorm.DB) error {
func DBInit(db *gorm.DB) error {
log.SetOutput(ioutil.Discard)
db.Callback().Delete().Replace("gorm:delete", hardDeleteCallback)
log.SetOutput(os.Stderr)
@@ -37,15 +40,14 @@ func dbInit(db *gorm.DB) error {
ID: "2",
Migrate: func(tx *gorm.DB) error {
type SSHKey struct {
// FIXME: use uuid for ID
gorm.Model
Name string
Type string
Length uint
Fingerprint string
PrivKey string `sql:"size:10000"`
PubKey string `sql:"size:10000"`
Hosts []*Host `gorm:"ForeignKey:SSHKeyID"`
PrivKey string `sql:"size:5000"`
PubKey string `sql:"size:1000"`
Hosts []*dbmodels.Host `gorm:"ForeignKey:SSHKeyID"`
Comment string
}
return tx.AutoMigrate(&SSHKey{}).Error
@@ -57,15 +59,14 @@ func dbInit(db *gorm.DB) error {
ID: "3",
Migrate: func(tx *gorm.DB) error {
type Host struct {
// FIXME: use uuid for ID
gorm.Model
Name string `gorm:"size:32"`
Addr string
User string
Password string
SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
SSHKey *dbmodels.SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
Groups []*dbmodels.HostGroup `gorm:"many2many:host_host_groups;"`
Fingerprint string
Comment string
}
@@ -79,9 +80,9 @@ func dbInit(db *gorm.DB) error {
Migrate: func(tx *gorm.DB) error {
type UserKey struct {
gorm.Model
Key []byte `sql:"size:10000"`
UserID uint ``
User *User `gorm:"ForeignKey:UserID"`
Key []byte `sql:"size:1000"`
UserID uint ``
User *dbmodels.User `gorm:"ForeignKey:UserID"`
Comment string
}
return tx.AutoMigrate(&UserKey{}).Error
@@ -93,13 +94,12 @@ func dbInit(db *gorm.DB) error {
ID: "5",
Migrate: func(tx *gorm.DB) error {
type User struct {
// FIXME: use uuid for ID
gorm.Model
IsAdmin bool
Email string
Name string
Keys []*UserKey `gorm:"ForeignKey:UserID"`
Groups []*UserGroup `gorm:"many2many:user_user_groups;"`
Keys []*dbmodels.UserKey `gorm:"ForeignKey:UserID"`
Groups []*dbmodels.UserGroup `gorm:"many2many:user_user_groups;"`
Comment string
InviteToken string
}
@@ -114,8 +114,8 @@ func dbInit(db *gorm.DB) error {
type UserGroup struct {
gorm.Model
Name string
Users []*User `gorm:"many2many:user_user_groups;"`
ACLs []*ACL `gorm:"many2many:user_group_acls;"`
Users []*dbmodels.User `gorm:"many2many:user_user_groups;"`
ACLs []*dbmodels.ACL `gorm:"many2many:user_group_acls;"`
Comment string
}
return tx.AutoMigrate(&UserGroup{}).Error
@@ -129,8 +129,8 @@ func dbInit(db *gorm.DB) error {
type HostGroup struct {
gorm.Model
Name string
Hosts []*Host `gorm:"many2many:host_host_groups;"`
ACLs []*ACL `gorm:"many2many:host_group_acls;"`
Hosts []*dbmodels.Host `gorm:"many2many:host_host_groups;"`
ACLs []*dbmodels.ACL `gorm:"many2many:host_group_acls;"`
Comment string
}
return tx.AutoMigrate(&HostGroup{}).Error
@@ -143,8 +143,8 @@ func dbInit(db *gorm.DB) error {
Migrate: func(tx *gorm.DB) error {
type ACL struct {
gorm.Model
HostGroups []*HostGroup `gorm:"many2many:host_group_acls;"`
UserGroups []*UserGroup `gorm:"many2many:user_group_acls;"`
HostGroups []*dbmodels.HostGroup `gorm:"many2many:host_group_acls;"`
UserGroups []*dbmodels.UserGroup `gorm:"many2many:user_group_acls;"`
HostPattern string
Action string
Weight uint
@@ -159,64 +159,64 @@ func dbInit(db *gorm.DB) error {
}, {
ID: "9",
Migrate: func(tx *gorm.DB) error {
db.Model(&Setting{}).RemoveIndex("uix_settings_name")
return db.Model(&Setting{}).AddUniqueIndex("uix_settings_name", "name").Error
db.Model(&dbmodels.Setting{}).RemoveIndex("uix_settings_name")
return db.Model(&dbmodels.Setting{}).AddUniqueIndex("uix_settings_name", "name").Error
},
Rollback: func(tx *gorm.DB) error {
return db.Model(&Setting{}).RemoveIndex("uix_settings_name").Error
return db.Model(&dbmodels.Setting{}).RemoveIndex("uix_settings_name").Error
},
}, {
ID: "10",
Migrate: func(tx *gorm.DB) error {
db.Model(&SSHKey{}).RemoveIndex("uix_keys_name")
return db.Model(&SSHKey{}).AddUniqueIndex("uix_keys_name", "name").Error
db.Model(&dbmodels.SSHKey{}).RemoveIndex("uix_keys_name")
return db.Model(&dbmodels.SSHKey{}).AddUniqueIndex("uix_keys_name", "name").Error
},
Rollback: func(tx *gorm.DB) error {
return db.Model(&SSHKey{}).RemoveIndex("uix_keys_name").Error
return db.Model(&dbmodels.SSHKey{}).RemoveIndex("uix_keys_name").Error
},
}, {
ID: "11",
Migrate: func(tx *gorm.DB) error {
db.Model(&Host{}).RemoveIndex("uix_hosts_name")
return db.Model(&Host{}).AddUniqueIndex("uix_hosts_name", "name").Error
db.Model(&dbmodels.Host{}).RemoveIndex("uix_hosts_name")
return db.Model(&dbmodels.Host{}).AddUniqueIndex("uix_hosts_name", "name").Error
},
Rollback: func(tx *gorm.DB) error {
return db.Model(&Host{}).RemoveIndex("uix_hosts_name").Error
return db.Model(&dbmodels.Host{}).RemoveIndex("uix_hosts_name").Error
},
}, {
ID: "12",
Migrate: func(tx *gorm.DB) error {
db.Model(&User{}).RemoveIndex("uix_users_name")
return db.Model(&User{}).AddUniqueIndex("uix_users_name", "name").Error
db.Model(&dbmodels.User{}).RemoveIndex("uix_users_name")
return db.Model(&dbmodels.User{}).AddUniqueIndex("uix_users_name", "name").Error
},
Rollback: func(tx *gorm.DB) error {
return db.Model(&User{}).RemoveIndex("uix_users_name").Error
return db.Model(&dbmodels.User{}).RemoveIndex("uix_users_name").Error
},
}, {
ID: "13",
Migrate: func(tx *gorm.DB) error {
db.Model(&UserGroup{}).RemoveIndex("uix_usergroups_name")
return db.Model(&UserGroup{}).AddUniqueIndex("uix_usergroups_name", "name").Error
db.Model(&dbmodels.UserGroup{}).RemoveIndex("uix_usergroups_name")
return db.Model(&dbmodels.UserGroup{}).AddUniqueIndex("uix_usergroups_name", "name").Error
},
Rollback: func(tx *gorm.DB) error {
return db.Model(&UserGroup{}).RemoveIndex("uix_usergroups_name").Error
return db.Model(&dbmodels.UserGroup{}).RemoveIndex("uix_usergroups_name").Error
},
}, {
ID: "14",
Migrate: func(tx *gorm.DB) error {
db.Model(&HostGroup{}).RemoveIndex("uix_hostgroups_name")
return db.Model(&HostGroup{}).AddUniqueIndex("uix_hostgroups_name", "name").Error
db.Model(&dbmodels.HostGroup{}).RemoveIndex("uix_hostgroups_name")
return db.Model(&dbmodels.HostGroup{}).AddUniqueIndex("uix_hostgroups_name", "name").Error
},
Rollback: func(tx *gorm.DB) error {
return db.Model(&HostGroup{}).RemoveIndex("uix_hostgroups_name").Error
return db.Model(&dbmodels.HostGroup{}).RemoveIndex("uix_hostgroups_name").Error
},
}, {
ID: "15",
Migrate: func(tx *gorm.DB) error {
type UserRole struct {
gorm.Model
Name string `valid:"required,length(1|32),unix_user"`
Users []*User `gorm:"many2many:user_user_roles"`
Name string `valid:"required,length(1|32),unix_user"`
Users []*dbmodels.User `gorm:"many2many:user_user_roles"`
}
return tx.AutoMigrate(&UserRole{}).Error
},
@@ -229,13 +229,13 @@ func dbInit(db *gorm.DB) error {
type User struct {
gorm.Model
IsAdmin bool
Roles []*UserRole `gorm:"many2many:user_user_roles"`
Email string `valid:"required,email"`
Name string `valid:"required,length(1|32),unix_user"`
Keys []*UserKey `gorm:"ForeignKey:UserID"`
Groups []*UserGroup `gorm:"many2many:user_user_groups;"`
Comment string `valid:"optional"`
InviteToken string `valid:"optional,length(10|60)"`
Roles []*dbmodels.UserRole `gorm:"many2many:user_user_roles"`
Email string `valid:"required,email"`
Name string `valid:"required,length(1|32),unix_user"`
Keys []*dbmodels.UserKey `gorm:"ForeignKey:UserID"`
Groups []*dbmodels.UserGroup `gorm:"many2many:user_user_groups;"`
Comment string `valid:"optional"`
InviteToken string `valid:"optional,length(10|60)"`
}
return tx.AutoMigrate(&User{}).Error
},
@@ -245,27 +245,27 @@ func dbInit(db *gorm.DB) error {
}, {
ID: "17",
Migrate: func(tx *gorm.DB) error {
return tx.Create(&UserRole{Name: "admin"}).Error
return tx.Create(&dbmodels.UserRole{Name: "admin"}).Error
},
Rollback: func(tx *gorm.DB) error {
return tx.Where("name = ?", "admin").Delete(&UserRole{}).Error
return tx.Where("name = ?", "admin").Delete(&dbmodels.UserRole{}).Error
},
}, {
ID: "18",
Migrate: func(tx *gorm.DB) error {
var adminRole UserRole
var adminRole dbmodels.UserRole
if err := db.Where("name = ?", "admin").First(&adminRole).Error; err != nil {
return err
}
var users []User
var users []*dbmodels.User
if err := db.Preload("Roles").Where("is_admin = ?", true).Find(&users).Error; err != nil {
return err
}
for _, user := range users {
user.Roles = append(user.Roles, &adminRole)
if err := tx.Save(&user).Error; err != nil {
if err := tx.Save(user).Error; err != nil {
return err
}
}
@@ -279,13 +279,13 @@ func dbInit(db *gorm.DB) error {
Migrate: func(tx *gorm.DB) error {
type User struct {
gorm.Model
Roles []*UserRole `gorm:"many2many:user_user_roles"`
Email string `valid:"required,email"`
Name string `valid:"required,length(1|32),unix_user"`
Keys []*UserKey `gorm:"ForeignKey:UserID"`
Groups []*UserGroup `gorm:"many2many:user_user_groups;"`
Comment string `valid:"optional"`
InviteToken string `valid:"optional,length(10|60)"`
Roles []*dbmodels.UserRole `gorm:"many2many:user_user_roles"`
Email string `valid:"required,email"`
Name string `valid:"required,length(1|32),unix_user"`
Keys []*dbmodels.UserKey `gorm:"ForeignKey:UserID"`
Groups []*dbmodels.UserGroup `gorm:"many2many:user_user_groups;"`
Comment string `valid:"optional"`
InviteToken string `valid:"optional,length(10|60)"`
}
return tx.AutoMigrate(&User{}).Error
},
@@ -295,24 +295,24 @@ func dbInit(db *gorm.DB) error {
}, {
ID: "20",
Migrate: func(tx *gorm.DB) error {
return tx.Create(&UserRole{Name: "listhosts"}).Error
return tx.Create(&dbmodels.UserRole{Name: "listhosts"}).Error
},
Rollback: func(tx *gorm.DB) error {
return tx.Where("name = ?", "listhosts").Delete(&UserRole{}).Error
return tx.Where("name = ?", "listhosts").Delete(&dbmodels.UserRole{}).Error
},
}, {
ID: "21",
Migrate: func(tx *gorm.DB) error {
type Session struct {
gorm.Model
StoppedAt time.Time `valid:"optional"`
Status string `valid:"required"`
User *User `gorm:"ForeignKey:UserID"`
Host *Host `gorm:"ForeignKey:HostID"`
UserID uint `valid:"optional"`
HostID uint `valid:"optional"`
ErrMsg string `valid:"optional"`
Comment string `valid:"optional"`
StoppedAt time.Time `valid:"optional"`
Status string `valid:"required"`
User *dbmodels.User `gorm:"ForeignKey:UserID"`
Host *dbmodels.Host `gorm:"ForeignKey:HostID"`
UserID uint `valid:"optional"`
HostID uint `valid:"optional"`
ErrMsg string `valid:"optional"`
Comment string `valid:"optional"`
}
return tx.AutoMigrate(&Session{}).Error
},
@@ -324,12 +324,12 @@ func dbInit(db *gorm.DB) error {
Migrate: func(tx *gorm.DB) error {
type Event struct {
gorm.Model
Author *User `gorm:"ForeignKey:AuthorID"`
AuthorID uint `valid:"optional"`
Domain string `valid:"required"`
Action string `valid:"required"`
Entity string `valid:"optional"`
Args []byte `sql:"size:10000" valid:"optional,length(1|10000)"`
Author *dbmodels.User `gorm:"ForeignKey:AuthorID"`
AuthorID uint `valid:"optional"`
Domain string `valid:"required"`
Action string `valid:"required"`
Entity string `valid:"optional"`
Args []byte `sql:"size:10000" valid:"optional,length(1|10000)"`
}
return tx.AutoMigrate(&Event{}).Error
},
@@ -341,11 +341,11 @@ func dbInit(db *gorm.DB) error {
Migrate: func(tx *gorm.DB) error {
type UserKey struct {
gorm.Model
Key []byte `sql:"size:10000" valid:"required,length(1|10000)"`
AuthorizedKey string `sql:"size:10000" valid:"required,length(1|10000)"`
UserID uint ``
User *User `gorm:"ForeignKey:UserID"`
Comment string `valid:"optional"`
Key []byte `sql:"size:1000" valid:"required,length(1|1000)"`
AuthorizedKey string `sql:"size:1000" valid:"required,length(1|1000)"`
UserID uint ``
User *dbmodels.User `gorm:"ForeignKey:UserID"`
Comment string `valid:"optional"`
}
return tx.AutoMigrate(&UserKey{}).Error
},
@@ -355,7 +355,7 @@ func dbInit(db *gorm.DB) error {
}, {
ID: "24",
Migrate: func(tx *gorm.DB) error {
var userKeys []UserKey
var userKeys []*dbmodels.UserKey
if err := db.Find(&userKeys).Error; err != nil {
return err
}
@@ -366,7 +366,7 @@ func dbInit(db *gorm.DB) error {
return err
}
userKey.AuthorizedKey = string(gossh.MarshalAuthorizedKey(key))
if err := db.Model(&userKey).Updates(&userKey).Error; err != nil {
if err := db.Model(userKey).Updates(userKey).Error; err != nil {
return err
}
}
@@ -379,18 +379,17 @@ func dbInit(db *gorm.DB) error {
ID: "25",
Migrate: func(tx *gorm.DB) error {
type Host struct {
// FIXME: use uuid for ID
gorm.Model
Name string `gorm:"size:32" valid:"required,length(1|32),unix_user"`
Addr string `valid:"required"`
User string `valid:"optional"`
Password string `valid:"optional"`
SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"` // SSHKey used to connect by the client
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:10000" valid:"optional"`
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
Fingerprint string `valid:"optional"` // FIXME: replace with hostKey ?
Comment string `valid:"optional"`
Name string `gorm:"size:32" valid:"required,length(1|32),unix_user"`
Addr string `valid:"required"`
User string `valid:"optional"`
Password string `valid:"optional"`
SSHKey *dbmodels.SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:1000" valid:"optional"`
Groups []*dbmodels.HostGroup `gorm:"many2many:host_host_groups;"`
Fingerprint string `valid:"optional"`
Comment string `valid:"optional"`
}
return tx.AutoMigrate(&Host{}).Error
},
@@ -402,14 +401,14 @@ func dbInit(db *gorm.DB) error {
Migrate: func(tx *gorm.DB) error {
type Session struct {
gorm.Model
StoppedAt *time.Time `sql:"index" valid:"optional"`
Status string `valid:"required"`
User *User `gorm:"ForeignKey:UserID"`
Host *Host `gorm:"ForeignKey:HostID"`
UserID uint `valid:"optional"`
HostID uint `valid:"optional"`
ErrMsg string `valid:"optional"`
Comment string `valid:"optional"`
StoppedAt *time.Time `sql:"index" valid:"optional"`
Status string `valid:"required"`
User *dbmodels.User `gorm:"ForeignKey:UserID"`
Host *dbmodels.Host `gorm:"ForeignKey:HostID"`
UserID uint `valid:"optional"`
HostID uint `valid:"optional"`
ErrMsg string `valid:"optional"`
Comment string `valid:"optional"`
}
return tx.AutoMigrate(&Session{}).Error
},
@@ -419,14 +418,14 @@ func dbInit(db *gorm.DB) error {
}, {
ID: "27",
Migrate: func(tx *gorm.DB) error {
var sessions []Session
var sessions []*dbmodels.Session
if err := db.Find(&sessions).Error; err != nil {
return err
}
for _, session := range sessions {
if session.StoppedAt != nil && session.StoppedAt.IsZero() {
if err := db.Model(&session).Updates(map[string]interface{}{"stopped_at": nil}).Error; err != nil {
if err := db.Model(session).Updates(map[string]interface{}{"stopped_at": nil}).Error; err != nil {
return err
}
}
@@ -440,17 +439,16 @@ func dbInit(db *gorm.DB) error {
ID: "28",
Migrate: func(tx *gorm.DB) error {
type Host struct {
// FIXME: use uuid for ID
gorm.Model
Name string `gorm:"size:32"`
Addr string
User string
Password string
URL string
SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:10000"`
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
SSHKey *dbmodels.SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:1000"`
Groups []*dbmodels.HostGroup `gorm:"many2many:host_host_groups;"`
Comment string
}
return tx.AutoMigrate(&Host{}).Error
@@ -462,19 +460,18 @@ func dbInit(db *gorm.DB) error {
ID: "29",
Migrate: func(tx *gorm.DB) error {
type Host struct {
// FIXME: use uuid for ID
gorm.Model
Name string `gorm:"size:32"`
Addr string
User string
Password string
URL string
SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:10000"`
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
SSHKey *dbmodels.SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:1000"`
Groups []*dbmodels.HostGroup `gorm:"many2many:host_host_groups;"`
Comment string
Hop *Host
Hop *dbmodels.Host
HopID uint
}
return tx.AutoMigrate(&Host{}).Error
@@ -482,12 +479,57 @@ func dbInit(db *gorm.DB) error {
Rollback: func(tx *gorm.DB) error {
return fmt.Errorf("not implemented")
},
}, {
ID: "30",
Migrate: func(tx *gorm.DB) error {
type Host struct {
gorm.Model
Name string `gorm:"size:32"`
Addr string
User string
Password string
URL string
SSHKey *dbmodels.SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:10000"`
Groups []*dbmodels.HostGroup `gorm:"many2many:host_host_groups;"`
Comment string
Hop *dbmodels.Host
Logging string
HopID uint
}
return tx.AutoMigrate(&Host{}).Error
},
Rollback: func(tx *gorm.DB) error { return fmt.Errorf("not implemented") },
}, {
ID: "31",
Migrate: func(tx *gorm.DB) error {
return tx.Model(&dbmodels.Host{}).Updates(&dbmodels.Host{Logging: "everything"}).Error
},
Rollback: func(tx *gorm.DB) error { return fmt.Errorf("not implemented") },
}, {
ID: "32",
Migrate: func(tx *gorm.DB) error {
type ACL struct {
gorm.Model
HostGroups []*dbmodels.HostGroup `gorm:"many2many:host_group_acls;"`
UserGroups []*dbmodels.UserGroup `gorm:"many2many:user_group_acls;"`
HostPattern string `valid:"optional"`
Action string `valid:"required"`
Weight uint ``
Comment string `valid:"optional"`
Inception *time.Time
Expiration *time.Time
}
return tx.AutoMigrate(&ACL{}).Error
},
Rollback: func(tx *gorm.DB) error { return fmt.Errorf("not implemented") },
},
})
if err := m.Migrate(); err != nil {
return err
}
NewEvent("system", "migrated").Log(db)
dbmodels.NewEvent("system", "migrated").Log(db)
// create default ssh key
var count uint
@@ -495,7 +537,7 @@ func dbInit(db *gorm.DB) error {
return err
}
if count == 0 {
key, err := NewSSHKey("rsa", 2048)
key, err := crypto.NewSSHKey("ed25519", 1)
if err != nil {
return err
}
@@ -511,7 +553,7 @@ func dbInit(db *gorm.DB) error {
return err
}
if count == 0 {
hostGroup := HostGroup{
hostGroup := dbmodels.HostGroup{
Name: "default",
Comment: "created by sshportal",
}
@@ -525,7 +567,7 @@ func dbInit(db *gorm.DB) error {
return err
}
if count == 0 {
userGroup := UserGroup{
userGroup := dbmodels.UserGroup{
Name: "default",
Comment: "created by sshportal",
}
@@ -539,13 +581,13 @@ func dbInit(db *gorm.DB) error {
return err
}
if count == 0 {
var defaultUserGroup UserGroup
var defaultUserGroup dbmodels.UserGroup
db.Where("name = ?", "default").First(&defaultUserGroup)
var defaultHostGroup HostGroup
var defaultHostGroup dbmodels.HostGroup
db.Where("name = ?", "default").First(&defaultHostGroup)
acl := ACL{
UserGroups: []*UserGroup{&defaultUserGroup},
HostGroups: []*HostGroup{&defaultHostGroup},
acl := dbmodels.ACL{
UserGroups: []*dbmodels.UserGroup{&defaultUserGroup},
HostGroups: []*dbmodels.HostGroup{&defaultHostGroup},
Action: "allow",
//HostPattern: "",
//Weight: 0,
@@ -557,7 +599,7 @@ func dbInit(db *gorm.DB) error {
}
// create admin user
var defaultUserGroup UserGroup
var defaultUserGroup dbmodels.UserGroup
db.Where("name = ?", "default").First(&defaultUserGroup)
if err := db.Table("users").Count(&count).Error; err != nil {
return err
@@ -568,7 +610,7 @@ func dbInit(db *gorm.DB) error {
if os.Getenv("SSHPORTAL_DEFAULT_ADMIN_INVITE_TOKEN") != "" {
inviteToken = os.Getenv("SSHPORTAL_DEFAULT_ADMIN_INVITE_TOKEN")
}
var adminRole UserRole
var adminRole dbmodels.UserRole
if err := db.Where("name = ?", "admin").First(&adminRole).Error; err != nil {
return err
}
@@ -583,13 +625,13 @@ func dbInit(db *gorm.DB) error {
if username == "" {
username = "admin" // fallback username
}
user := User{
user := dbmodels.User{
Name: username,
Email: fmt.Sprintf("%s@localhost", username),
Comment: "created by sshportal",
Roles: []*UserRole{&adminRole},
Roles: []*dbmodels.UserRole{&adminRole},
InviteToken: inviteToken,
Groups: []*UserGroup{&defaultUserGroup},
Groups: []*dbmodels.UserGroup{&defaultUserGroup},
}
if err := db.Create(&user).Error; err != nil {
return err
@@ -602,7 +644,7 @@ func dbInit(db *gorm.DB) error {
return err
}
if count == 0 {
key, err := NewSSHKey("rsa", 2048)
key, err := crypto.NewSSHKey("ed25519", 1)
if err != nil {
return err
}
@@ -614,8 +656,8 @@ func dbInit(db *gorm.DB) error {
}
// close unclosed connections
return db.Table("sessions").Where("status = ?", "active").Updates(&Session{
Status: string(SessionStatusClosed),
return db.Table("sessions").Where("status = ?", "active").Updates(&dbmodels.Session{
Status: string(dbmodels.SessionStatusClosed),
ErrMsg: "sshportal was halted while the connection was still active",
}).Error
}
@@ -643,3 +685,13 @@ func addExtraSpaceIfExist(str string) string {
}
return ""
}
func randStringBytes(n int) string {
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}

View File

@@ -1,4 +1,4 @@
package logtunnel // import "moul.io/sshportal/pkg/logtunnel"
package bastion // import "moul.io/sshportal/pkg/bastion"
import (
"encoding/binary"
@@ -17,7 +17,7 @@ type logTunnel struct {
writer io.WriteCloser
}
type ForwardData struct {
type logTunnelForwardData struct {
DestinationHost string
DestinationPort uint32
SourceHost string
@@ -40,7 +40,7 @@ func writeHeader(fd io.Writer, length int) {
}
}
func New(channel ssh.Channel, writer io.WriteCloser, host string) io.ReadWriteCloser {
func newLogTunnel(channel ssh.Channel, writer io.WriteCloser, host string) io.ReadWriteCloser {
return &logTunnel{
host: host,
channel: channel,

View File

@@ -1,34 +1,28 @@
package bastionsession // import "moul.io/sshportal/pkg/bastionsession"
package bastion // import "moul.io/sshportal/pkg/bastion"
import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"path/filepath"
"time"
"github.com/moul/ssh"
"github.com/gliderlabs/ssh"
"github.com/pkg/errors"
"github.com/sabban/bastion/pkg/logchannel"
gossh "golang.org/x/crypto/ssh"
"moul.io/sshportal/pkg/logtunnel"
)
type ForwardData struct {
DestinationHost string
DestinationPort uint32
SourceHost string
SourcePort uint32
}
type Config struct {
type sessionConfig struct {
Addr string
Logs string
LogsLocation string
ClientConfig *gossh.ClientConfig
LoggingMode string
}
func MultiChannelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context, configs []Config) error {
func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context, configs []sessionConfig, sessionID uint) error {
var lastClient *gossh.Client
switch newChan.ChannelType() {
case "session":
@@ -56,6 +50,7 @@ func MultiChannelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.
client = gossh.NewClient(ncc, chans, reqs)
}
if err != nil {
lch.Close() // fix #56
return err
}
defer func() { _ = client.Close() }()
@@ -67,8 +62,10 @@ func MultiChannelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.
return err
}
user := conn.User()
actx := ctx.Value(authContextKey).(*authContext)
username := actx.user.Name
// pipe everything
return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1].Logs, user, newChan)
return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan)
case "direct-tcpip":
lch, lreqs, err := newChan.Accept()
// TODO: defer clean closer
@@ -94,13 +91,14 @@ func MultiChannelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.
client = gossh.NewClient(ncc, chans, reqs)
}
if err != nil {
lch.Close()
return err
}
defer func() { _ = client.Close() }()
lastClient = client
}
d := logtunnel.ForwardData{}
d := logTunnelForwardData{}
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
return err
}
@@ -109,8 +107,10 @@ func MultiChannelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.
return err
}
user := conn.User()
actx := ctx.Value(authContextKey).(*authContext)
username := actx.user.Name
// pipe everything
return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1].Logs, user, newChan)
return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan)
default:
if err := newChan.Reject(gossh.UnknownChannelType, "unsupported channel type"); err != nil {
log.Printf("failed to reject chan: %v", err)
@@ -119,65 +119,77 @@ func MultiChannelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.
}
}
func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, logsLocation string, user string, newChan gossh.NewChannel) error {
func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, sessConfig sessionConfig, user string, username string, sessionID uint, newChan gossh.NewChannel) error {
defer func() {
_ = lch.Close()
_ = rch.Close()
}()
errch := make(chan error, 1)
quit := make(chan string, 1)
channeltype := newChan.ChannelType()
filename := strings.Join([]string{logsLocation, "/", user, "-", channeltype, "-", time.Now().Format(time.RFC3339)}, "") // get user
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
defer func() {
_ = f.Close()
}()
if err != nil {
log.Fatalf("error: %v", err)
var logWriter io.WriteCloser = newDiscardWriteCloser()
if sessConfig.LoggingMode != "disabled" {
filename := filepath.Join(sessConfig.LogsLocation, fmt.Sprintf("%s-%s-%s-%d-%s", user, username, channeltype, sessionID, time.Now().Format(time.RFC3339)))
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0440)
if err != nil {
return errors.Wrap(err, "open log file")
}
defer func() {
_ = f.Close()
}()
log.Printf("Session %v is recorded in %v", channeltype, filename)
logWriter = f
}
log.Printf("Session %v is recorded in %v", channeltype, filename)
if channeltype == "session" {
wrappedlch := logchannel.New(lch, f)
go func() {
_, _ = io.Copy(wrappedlch, rch)
errch <- errors.New("lch closed the connection")
}()
go func() {
_, _ = io.Copy(rch, lch)
errch <- errors.New("rch closed the connection")
}()
switch sessConfig.LoggingMode {
case "input":
wrappedrch := logchannel.New(rch, logWriter)
go func(quit chan string) {
_, _ = io.Copy(lch, rch)
quit <- "rch"
}(quit)
go func(quit chan string) {
_, _ = io.Copy(wrappedrch, lch)
quit <- "lch"
}(quit)
default: // everything, disabled
wrappedlch := logchannel.New(lch, logWriter)
go func(quit chan string) {
_, _ = io.Copy(wrappedlch, rch)
quit <- "rch"
}(quit)
go func(quit chan string) {
_, _ = io.Copy(rch, lch)
quit <- "lch"
}(quit)
}
}
if channeltype == "direct-tcpip" {
d := logtunnel.ForwardData{}
d := logTunnelForwardData{}
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
return err
}
wrappedlch := logtunnel.New(lch, f, d.SourceHost)
wrappedrch := logtunnel.New(rch, f, d.DestinationHost)
go func() {
wrappedlch := newLogTunnel(lch, logWriter, d.SourceHost)
wrappedrch := newLogTunnel(rch, logWriter, d.DestinationHost)
go func(quit chan string) {
_, _ = io.Copy(wrappedlch, rch)
errch <- errors.New("lch closed the connection")
}()
quit <- "rch"
}(quit)
go func() {
go func(quit chan string) {
_, _ = io.Copy(wrappedrch, lch)
errch <- errors.New("rch closed the connection")
}()
quit <- "lch"
}(quit)
}
for {
select {
case req := <-lreqs: // forward ssh requests from local to remote
if req == nil {
return nil
}
go func(quit chan string) {
for req := range lreqs {
b, err := rch.SendRequest(req.Type, req.WantReply, req.Payload)
if req.Type == "exec" {
wrappedlch := logchannel.New(lch, f)
wrappedlch := logchannel.New(lch, logWriter)
command := append(req.Payload, []byte("\n")...)
if _, err := wrappedlch.LogWrite(command); err != nil {
log.Printf("failed to write log: %v", err)
@@ -185,24 +197,68 @@ func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, logsLocati
}
if err != nil {
return err
errch <- err
}
if err2 := req.Reply(b, nil); err2 != nil {
return err2
}
case req := <-rreqs: // forward ssh requests from remote to local
if req == nil {
return nil
errch <- err2
}
}
quit <- "lreqs"
}(quit)
go func(quit chan string) {
for req := range rreqs {
b, err := lch.SendRequest(req.Type, req.WantReply, req.Payload)
if err != nil {
return err
errch <- err
}
if err2 := req.Reply(b, nil); err2 != nil {
return err2
errch <- err2
}
}
quit <- "rreqs"
}(quit)
lchEOF, rchEOF, lchClosed, rchClosed := false, false, false, false
for {
select {
case err := <-errch:
return err
case q := <-quit:
switch q {
case "lch":
lchEOF = true
_ = rch.CloseWrite()
case "rch":
rchEOF = true
_ = lch.CloseWrite()
case "lreqs":
lchClosed = true
case "rreqs":
rchClosed = true
}
if lchEOF && lchClosed && !rchClosed {
rch.Close()
}
if rchEOF && rchClosed && !lchClosed {
lch.Close()
}
if lchEOF && rchEOF && lchClosed && rchClosed {
return nil
}
}
}
}
func newDiscardWriteCloser() io.WriteCloser { return &discardWriteCloser{ioutil.Discard} }
type discardWriteCloser struct {
io.Writer
}
func (discardWriteCloser) Close() error {
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
package main
package bastion // import "moul.io/sshportal/pkg/bastion"
import (
"bytes"
@@ -9,11 +9,11 @@ import (
"strings"
"time"
"github.com/gliderlabs/ssh"
"github.com/jinzhu/gorm"
"github.com/moul/ssh"
gossh "golang.org/x/crypto/ssh"
"moul.io/sshportal/pkg/bastionsession"
"moul.io/sshportal/pkg/crypto"
"moul.io/sshportal/pkg/dbmodels"
)
type sshportalContextKey string
@@ -21,40 +21,45 @@ type sshportalContextKey string
var authContextKey = sshportalContextKey("auth")
type authContext struct {
message string
err error
user User
inputUsername string
db *gorm.DB
userKey UserKey
config *configServe
authMethod string
authSuccess bool
message string
err error
user dbmodels.User
inputUsername string
db *gorm.DB
userKey dbmodels.UserKey
logsLocation string
aclCheckCmd string
aesKey string
dbDriver, dbURL string
bindAddr string
demo, debug bool
authMethod string
authSuccess bool
}
type UserType string
type userType string
const (
UserTypeHealthcheck UserType = "healthcheck"
UserTypeBastion UserType = "bastion"
UserTypeInvite UserType = "invite"
UserTypeShell UserType = "shell"
userTypeHealthcheck userType = "healthcheck"
userTypeBastion userType = "bastion"
userTypeInvite userType = "invite"
userTypeShell userType = "shell"
)
func (c authContext) userType() UserType {
func (c authContext) userType() userType {
switch {
case c.inputUsername == "healthcheck":
return UserTypeHealthcheck
return userTypeHealthcheck
case c.inputUsername == c.user.Name || c.inputUsername == c.user.Email || c.inputUsername == "admin":
return UserTypeShell
return userTypeShell
case strings.HasPrefix(c.inputUsername, "invite:"):
return UserTypeInvite
return userTypeInvite
default:
return UserTypeBastion
return userTypeBastion
}
}
func dynamicHostKey(db *gorm.DB, host *Host) gossh.HostKeyCallback {
func dynamicHostKey(db *gorm.DB, host *dbmodels.Host) gossh.HostKeyCallback {
return func(hostname string, remote net.Addr, key gossh.PublicKey) error {
if len(host.HostKey) == 0 {
log.Println("Discovering host fingerprint...")
@@ -68,7 +73,9 @@ func dynamicHostKey(db *gorm.DB, host *Host) gossh.HostKeyCallback {
}
}
func channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
var DefaultChannelHandler ssh.ChannelHandler = func(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {}
func ChannelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
switch newChan.ChannelType() {
case "session":
case "direct-tcpip":
@@ -83,9 +90,9 @@ func channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewCh
actx := ctx.Value(authContextKey).(*authContext)
switch actx.userType() {
case UserTypeBastion:
log.Printf("New connection(bastion): sshUser=%q remote=%q local=%q dbUser=id:%q,email:%s", conn.User(), conn.RemoteAddr(), conn.LocalAddr(), actx.user.ID, actx.user.Email)
host, err := HostByName(actx.db, actx.inputUsername)
case userTypeBastion:
log.Printf("New connection(bastion): sshUser=%q remote=%q local=%q dbUser=id:%d,email:%s", conn.User(), conn.RemoteAddr(), conn.LocalAddr(), actx.user.ID, actx.user.Email)
host, err := dbmodels.HostByName(actx.db, actx.inputUsername)
if err != nil {
ch, _, err2 := newChan.Accept()
if err2 != nil {
@@ -98,8 +105,8 @@ func channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewCh
}
switch host.Scheme() {
case BastionSchemeSSH:
sessionConfigs := make([]bastionsession.Config, 0)
case dbmodels.BastionSchemeSSH:
sessionConfigs := make([]sessionConfig, 0)
currentHost := host
for currentHost != nil {
clientConfig, err2 := bastionClientConfig(ctx, currentHost)
@@ -113,25 +120,26 @@ func channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewCh
_ = ch.Close()
return
}
sessionConfigs = append([]bastionsession.Config{{
sessionConfigs = append([]sessionConfig{{
Addr: currentHost.DialAddr(),
ClientConfig: clientConfig,
Logs: actx.config.logsLocation,
LogsLocation: actx.logsLocation,
LoggingMode: currentHost.Logging,
}}, sessionConfigs...)
if currentHost.HopID != 0 {
var newHost Host
var newHost dbmodels.Host
actx.db.Model(currentHost).Related(&newHost, "HopID")
hostname := newHost.Name
currentHost, _ = HostByName(actx.db, hostname)
currentHost, _ = dbmodels.HostByName(actx.db, hostname)
} else {
currentHost = nil
}
}
sess := Session{
sess := dbmodels.Session{
UserID: actx.user.ID,
HostID: host.ID,
Status: string(SessionStatusActive),
Status: string(dbmodels.SessionStatusActive),
}
if err = actx.db.Create(&sess).Error; err != nil {
ch, _, err2 := newChan.Accept()
@@ -142,31 +150,29 @@ func channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewCh
_ = ch.Close()
return
}
go func() {
err = bastionsession.MultiChannelHandler(srv, conn, newChan, ctx, sessionConfigs)
err = multiChannelHandler(conn, newChan, ctx, sessionConfigs, sess.ID)
if err != nil {
log.Printf("Error: %v", err)
}
now := time.Now()
sessUpdate := Session{
Status: string(SessionStatusClosed),
sessUpdate := dbmodels.Session{
Status: string(dbmodels.SessionStatusClosed),
ErrMsg: fmt.Sprintf("%v", err),
StoppedAt: &now,
}
switch sessUpdate.ErrMsg {
case "lch closed the connection", "rch closed the connection":
if err == nil {
sessUpdate.ErrMsg = ""
}
actx.db.Model(&sess).Updates(&sessUpdate)
}()
case BastionSchemeTelnet:
case dbmodels.BastionSchemeTelnet:
tmpSrv := ssh.Server{
// PtyCallback: srv.PtyCallback,
Handler: telnetHandler(host),
}
defaultChannelHandler(&tmpSrv, conn, newChan, ctx)
DefaultChannelHandler(&tmpSrv, conn, newChan, ctx)
default:
ch, _, err2 := newChan.Accept()
if err2 != nil {
@@ -177,37 +183,35 @@ func channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewCh
_ = ch.Close()
}
default: // shell
defaultChannelHandler(srv, conn, newChan, ctx)
DefaultChannelHandler(srv, conn, newChan, ctx)
}
}
func bastionClientConfig(ctx ssh.Context, host *Host) (*gossh.ClientConfig, error) {
func bastionClientConfig(ctx ssh.Context, host *dbmodels.Host) (*gossh.ClientConfig, error) {
actx := ctx.Value(authContextKey).(*authContext)
clientConfig, err := host.clientConfig(dynamicHostKey(actx.db, host))
crypto.HostDecrypt(actx.aesKey, host)
crypto.SSHKeyDecrypt(actx.aesKey, host.SSHKey)
clientConfig, err := host.ClientConfig(dynamicHostKey(actx.db, host))
if err != nil {
return nil, err
}
var tmpUser User
var tmpUser dbmodels.User
if err = actx.db.Preload("Groups").Preload("Groups.ACLs").Where("id = ?", actx.user.ID).First(&tmpUser).Error; err != nil {
return nil, err
}
var tmpHost Host
var tmpHost dbmodels.Host
if err = actx.db.Preload("Groups").Preload("Groups.ACLs").Where("id = ?", host.ID).First(&tmpHost).Error; err != nil {
return nil, err
}
action, err2 := CheckACLs(tmpUser, tmpHost)
if err2 != nil {
return nil, err2
}
HostDecrypt(actx.config.aesKey, host)
SSHKeyDecrypt(actx.config.aesKey, host.SSHKey)
action := checkACLs(tmpUser, tmpHost, actx.aclCheckCmd)
switch action {
case string(ACLActionAllow):
case string(ACLActionDeny):
case string(dbmodels.ACLActionAllow):
// do nothing
case string(dbmodels.ACLActionDeny):
return nil, fmt.Errorf("you don't have permission to that host")
default:
return nil, fmt.Errorf("invalid ACL action: %q", action)
@@ -215,10 +219,10 @@ func bastionClientConfig(ctx ssh.Context, host *Host) (*gossh.ClientConfig, erro
return clientConfig, nil
}
func shellHandler(s ssh.Session) {
func ShellHandler(s ssh.Session, version, gitSha, gitTag string) {
actx := s.Context().Value(authContextKey).(*authContext)
if actx.userType() != UserTypeHealthcheck {
log.Printf("New connection(shell): sshUser=%q remote=%q local=%q command=%q dbUser=id:%q,email:%s", s.User(), s.RemoteAddr(), s.LocalAddr(), s.Command(), actx.user.ID, actx.user.Email)
if actx.userType() != userTypeHealthcheck {
log.Printf("New connection(shell): sshUser=%q remote=%q local=%q command=%q dbUser=id:%d,email:%s", s.User(), s.RemoteAddr(), s.LocalAddr(), s.Command(), actx.user.ID, actx.user.Email)
}
if actx.err != nil {
@@ -232,43 +236,49 @@ func shellHandler(s ssh.Session) {
}
switch actx.userType() {
case UserTypeHealthcheck:
case userTypeHealthcheck:
fmt.Fprintln(s, "OK")
return
case UserTypeShell:
if err := shell(s); err != nil {
case userTypeShell:
if err := shell(s, version, gitSha, gitTag); err != nil {
fmt.Fprintf(s, "error: %v\n", err)
_ = s.Exit(1)
}
return
case UserTypeInvite:
case userTypeInvite:
// do nothing (message was printed at the beginning of the function)
return
}
panic("should not happen")
}
func passwordAuthHandler(db *gorm.DB, cfg *configServe) ssh.PasswordHandler {
func PasswordAuthHandler(db *gorm.DB, logsLocation, aclCheckCmd, aesKey, dbDriver, dbURL, bindAddr string, demo bool) ssh.PasswordHandler {
return func(ctx ssh.Context, pass string) bool {
actx := &authContext{
db: db,
inputUsername: ctx.User(),
config: cfg,
logsLocation: logsLocation,
aclCheckCmd: aclCheckCmd,
aesKey: aesKey,
dbDriver: dbDriver,
dbURL: dbURL,
bindAddr: bindAddr,
demo: demo,
authMethod: "password",
}
actx.authSuccess = actx.userType() == UserTypeHealthcheck
actx.authSuccess = actx.userType() == userTypeHealthcheck
ctx.SetValue(authContextKey, actx)
return actx.authSuccess
}
}
func privateKeyFromDB(db *gorm.DB, aesKey string) func(*ssh.Server) error {
func PrivateKeyFromDB(db *gorm.DB, aesKey string) func(*ssh.Server) error {
return func(srv *ssh.Server) error {
var key SSHKey
if err := SSHKeysByIdentifiers(db, []string{"host"}).First(&key).Error; err != nil {
var key dbmodels.SSHKey
if err := dbmodels.SSHKeysByIdentifiers(db, []string{"host"}).First(&key).Error; err != nil {
return err
}
SSHKeyDecrypt(aesKey, &key)
crypto.SSHKeyDecrypt(aesKey, &key)
signer, err := gossh.ParsePrivateKey([]byte(key.PrivKey))
if err != nil {
@@ -279,12 +289,18 @@ func privateKeyFromDB(db *gorm.DB, aesKey string) func(*ssh.Server) error {
}
}
func publicKeyAuthHandler(db *gorm.DB, cfg *configServe) ssh.PublicKeyHandler {
func PublicKeyAuthHandler(db *gorm.DB, logsLocation, aclCheckCmd, aesKey, dbDriver, dbURL, bindAddr string, demo bool) ssh.PublicKeyHandler {
return func(ctx ssh.Context, key ssh.PublicKey) bool {
actx := &authContext{
db: db,
inputUsername: ctx.User(),
config: cfg,
logsLocation: logsLocation,
aclCheckCmd: aclCheckCmd,
aesKey: aesKey,
dbDriver: dbDriver,
dbURL: dbURL,
bindAddr: bindAddr,
demo: demo,
authMethod: "pubkey",
authSuccess: true,
}
@@ -294,20 +310,20 @@ func publicKeyAuthHandler(db *gorm.DB, cfg *configServe) ssh.PublicKeyHandler {
db.Where("authorized_key = ?", string(gossh.MarshalAuthorizedKey(key))).First(&actx.userKey)
if actx.userKey.UserID > 0 {
db.Preload("Roles").Where("id = ?", actx.userKey.UserID).First(&actx.user)
if actx.userType() == UserTypeInvite {
if actx.userType() == userTypeInvite {
actx.err = fmt.Errorf("invites are only supported for new SSH keys; your ssh key is already associated with the user %q", actx.user.Email)
}
return true
}
// handle invite "links"
if actx.userType() == UserTypeInvite {
if actx.userType() == userTypeInvite {
inputToken := strings.Split(actx.inputUsername, ":")[1]
if len(inputToken) > 0 {
db.Where("invite_token = ?", inputToken).First(&actx.user)
}
if actx.user.ID > 0 {
actx.userKey = UserKey{
actx.userKey = dbmodels.UserKey{
UserID: actx.user.ID,
Key: key.Marshal(),
Comment: "created by sshportal",
@@ -321,7 +337,7 @@ func publicKeyAuthHandler(db *gorm.DB, cfg *configServe) ssh.PublicKeyHandler {
actx.message = fmt.Sprintf("Welcome %s!\n\nYour key is now associated with the user %q.\n", actx.user.Name, actx.user.Email)
} else {
actx.user = User{Name: "Anonymous"}
actx.user = dbmodels.User{Name: "Anonymous"}
actx.err = errors.New("your token is invalid or expired")
}
return true
@@ -329,7 +345,7 @@ func publicKeyAuthHandler(db *gorm.DB, cfg *configServe) ssh.PublicKeyHandler {
// fallback
actx.err = errors.New("unknown ssh key")
actx.user = User{Name: "Anonymous"}
actx.user = dbmodels.User{Name: "Anonymous"}
return true
}
}

View File

@@ -1,4 +1,4 @@
package main
package bastion // import "moul.io/sshportal/pkg/bastion"
import (
"bufio"
@@ -8,9 +8,10 @@ import (
"log"
"time"
"github.com/moul/ssh"
"github.com/gliderlabs/ssh"
oi "github.com/reiver/go-oi"
telnet "github.com/reiver/go-telnet"
"moul.io/sshportal/pkg/dbmodels"
)
type bastionTelnetCaller struct {
@@ -75,10 +76,10 @@ func scannerSplitFunc(data []byte, atEOF bool) (advance int, token []byte, err e
return bufio.ScanLines(data, atEOF)
}
func telnetHandler(host *Host) ssh.Handler {
func telnetHandler(host *dbmodels.Host) ssh.Handler {
return func(s ssh.Session) {
// FIXME: log session in db
//actx := s.Context().Value(authContextKey).(*authContext)
// actx := s.Context().Value(authContextKey).(*authContext)
caller := bastionTelnetCaller{ssh: s}
if err := telnet.DialToAndCall(host.DialAddr(), caller); err != nil {
fmt.Fprintf(s, "error: %v", err)

View File

@@ -1,9 +1,12 @@
package main
package crypto // import "moul.io/sshportal/pkg/crypto"
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@@ -15,46 +18,120 @@ import (
"strings"
gossh "golang.org/x/crypto/ssh"
"moul.io/sshportal/pkg/dbmodels"
)
func NewSSHKey(keyType string, length uint) (*SSHKey, error) {
key := SSHKey{
func NewSSHKey(keyType string, length uint) (*dbmodels.SSHKey, error) {
key := dbmodels.SSHKey{
Type: keyType,
Length: length,
}
// generate the private key
if keyType != "rsa" {
return nil, fmt.Errorf("key type not supported: %q", key.Type)
var err error
var pemKey *pem.Block
var publicKey gossh.PublicKey
switch keyType {
case "rsa":
pemKey, publicKey, err = NewRSAKey(length)
case "ecdsa":
pemKey, publicKey, err = NewECDSAKey(length)
case "ed25519":
pemKey, publicKey, err = NewEd25519Key()
default:
return nil, fmt.Errorf("key type not supported: %q, supported types are: rsa, ecdsa, ed25519", key.Type)
}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
// convert priv key to x509 format
var pemKey = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
buf := bytes.NewBufferString("")
if err = pem.Encode(buf, pemKey); err != nil {
return nil, err
}
key.PrivKey = buf.String()
// generte authorized-key formatted pubkey output
pub, err := gossh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return nil, err
}
key.PubKey = strings.TrimSpace(string(gossh.MarshalAuthorizedKey(pub)))
// generate authorized-key formatted pubkey output
key.PubKey = strings.TrimSpace(string(gossh.MarshalAuthorizedKey(publicKey)))
return &key, nil
}
func ImportSSHKey(keyValue string) (*SSHKey, error) {
key := SSHKey{
func NewRSAKey(length uint) (*pem.Block, gossh.PublicKey, error) {
if length < 1024 || length > 16384 {
return nil, nil, fmt.Errorf("key length not supported: %d, supported values are between 1024 and 16384", length)
}
privateKey, err := rsa.GenerateKey(rand.Reader, int(length))
if err != nil {
return nil, nil, err
}
// convert priv key to x509 format
pemKey := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
publicKey, err := gossh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return nil, nil, err
}
return pemKey, publicKey, err
}
func NewECDSAKey(length uint) (*pem.Block, gossh.PublicKey, error) {
var curve elliptic.Curve
switch length {
case 256:
curve = elliptic.P256()
case 384:
curve = elliptic.P384()
case 521:
curve = elliptic.P521()
default:
return nil, nil, fmt.Errorf("key length not supported: %d, supported values are 256, 384, 521", length)
}
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, err
}
// convert priv key to x509 format
marshaledKey, err := x509.MarshalPKCS8PrivateKey(privateKey)
pemKey := &pem.Block{
Type: "PRIVATE KEY",
Bytes: marshaledKey,
}
if err != nil {
return nil, nil, err
}
publicKey, err := gossh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return nil, nil, err
}
return pemKey, publicKey, err
}
func NewEd25519Key() (*pem.Block, gossh.PublicKey, error) {
publicKeyEd25519, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
// convert priv key to x509 format
marshaledKey, err := x509.MarshalPKCS8PrivateKey(privateKey)
pemKey := &pem.Block{
Type: "PRIVATE KEY",
Bytes: marshaledKey,
}
if err != nil {
return nil, nil, err
}
publicKey, err := gossh.NewPublicKey(publicKeyEd25519)
if err != nil {
return nil, nil, err
}
return pemKey, publicKey, err
}
func ImportSSHKey(keyValue string) (*dbmodels.SSHKey, error) {
key := dbmodels.SSHKey{
Type: "rsa",
}
@@ -132,7 +209,7 @@ func safeDecrypt(key []byte, cryptoText string) string {
return out
}
func HostEncrypt(aesKey string, host *Host) (err error) {
func HostEncrypt(aesKey string, host *dbmodels.Host) (err error) {
if aesKey == "" {
return nil
}
@@ -141,7 +218,7 @@ func HostEncrypt(aesKey string, host *Host) (err error) {
}
return
}
func HostDecrypt(aesKey string, host *Host) {
func HostDecrypt(aesKey string, host *dbmodels.Host) {
if aesKey == "" {
return
}
@@ -150,14 +227,14 @@ func HostDecrypt(aesKey string, host *Host) {
}
}
func SSHKeyEncrypt(aesKey string, key *SSHKey) (err error) {
func SSHKeyEncrypt(aesKey string, key *dbmodels.SSHKey) (err error) {
if aesKey == "" {
return nil
}
key.PrivKey, err = encrypt([]byte(aesKey), key.PrivKey)
return
}
func SSHKeyDecrypt(aesKey string, key *SSHKey) {
func SSHKeyDecrypt(aesKey string, key *dbmodels.SSHKey) {
if aesKey == "" {
return
}

View File

@@ -1,16 +1,14 @@
package main
package dbmodels // import "moul.io/sshportal/pkg/dbmodels"
import (
"encoding/json"
"fmt"
"log"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"github.com/asaskevich/govalidator"
"github.com/jinzhu/gorm"
gossh "golang.org/x/crypto/ssh"
)
@@ -40,12 +38,12 @@ type Setting struct {
type SSHKey struct {
// FIXME: use uuid for ID
gorm.Model
Name string `valid:"required,length(1|32),unix_user"`
Name string `valid:"required,length(1|255),unix_user"`
Type string `valid:"required"`
Length uint `valid:"required"`
Fingerprint string `valid:"optional"`
PrivKey string `sql:"size:10000" valid:"required"`
PubKey string `sql:"size:10000" valid:"optional"`
PrivKey string `sql:"size:5000" valid:"required"`
PubKey string `sql:"size:1000" valid:"optional"`
Hosts []*Host `gorm:"ForeignKey:SSHKeyID"`
Comment string `valid:"optional"`
}
@@ -53,16 +51,17 @@ type SSHKey struct {
type Host struct {
// FIXME: use uuid for ID
gorm.Model
Name string `gorm:"size:32" valid:"required,length(1|32),unix_user"`
Name string `gorm:"size:255" valid:"required,length(1|255)"`
Addr string `valid:"optional"` // FIXME: to be removed in a future version in favor of URL
User string `valid:"optional"` // FIXME: to be removed in a future version in favor of URL
Password string `valid:"optional"` // FIXME: to be removed in a future version in favor of URL
URL string `valid:"optional"`
SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"` // SSHKey used to connect by the client
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:10000" valid:"optional"`
HostKey []byte `sql:"size:1000" valid:"optional"`
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
Comment string `valid:"optional"`
Logging string `valid:"optional,host_logging_mode"`
Hop *Host
HopID uint
}
@@ -70,8 +69,8 @@ type Host struct {
// UserKey defines a user public key used by sshportal to identify the user
type UserKey struct {
gorm.Model
Key []byte `sql:"size:10000" valid:"required,length(1|10000)"`
AuthorizedKey string `sql:"size:10000" valid:"required,length(1|10000)"`
Key []byte `sql:"size:1000" valid:"length(1|1000)"`
AuthorizedKey string `sql:"size:1000" valid:"required,length(1|1000)"`
UserID uint ``
User *User `gorm:"ForeignKey:UserID"`
Comment string `valid:"optional"`
@@ -79,7 +78,7 @@ type UserKey struct {
type UserRole struct {
gorm.Model
Name string `valid:"required,length(1|32),unix_user"`
Name string `valid:"required,length(1|255),unix_user"`
Users []*User `gorm:"many2many:user_user_roles"`
}
@@ -88,7 +87,7 @@ type User struct {
gorm.Model
Roles []*UserRole `gorm:"many2many:user_user_roles"`
Email string `valid:"required,email"`
Name string `valid:"required,length(1|32),unix_user"`
Name string `valid:"required,length(1|255),unix_user"`
Keys []*UserKey `gorm:"ForeignKey:UserID"`
Groups []*UserGroup `gorm:"many2many:user_user_groups;"`
Comment string `valid:"optional"`
@@ -97,7 +96,7 @@ type User struct {
type UserGroup struct {
gorm.Model
Name string `valid:"required,length(1|32),unix_user"`
Name string `valid:"required,length(1|255),unix_user"`
Users []*User `gorm:"many2many:user_user_groups;"`
ACLs []*ACL `gorm:"many2many:user_group_acls;"`
Comment string `valid:"optional"`
@@ -105,7 +104,7 @@ type UserGroup struct {
type HostGroup struct {
gorm.Model
Name string `valid:"required,length(1|32),unix_user"`
Name string `valid:"required,length(1|255),unix_user"`
Hosts []*Host `gorm:"many2many:host_host_groups;"`
ACLs []*ACL `gorm:"many2many:host_group_acls;"`
Comment string `valid:"optional"`
@@ -119,6 +118,8 @@ type ACL struct {
Action string `valid:"required"`
Weight uint ``
Comment string `valid:"optional"`
Inception *time.Time
Expiration *time.Time
}
type Session struct {
@@ -166,30 +167,27 @@ const (
BastionSchemeTelnet BastionScheme = "telnet"
)
func init() {
unixUserRegexp := regexp.MustCompile("[a-z_][a-z0-9_-]*")
govalidator.CustomTypeTagMap.Set("unix_user", govalidator.CustomTypeValidator(func(i interface{}, context interface{}) bool {
name, ok := i.(string)
if !ok {
return false
// Generic Helper
func GenericNameOrID(db *gorm.DB, identifiers []string) *gorm.DB {
var ids []string
var names []string
for _, s := range identifiers {
if _, err := strconv.Atoi(s); err == nil {
ids = append(ids, s)
} else {
names = append(names, s)
}
return unixUserRegexp.MatchString(name)
}))
}
if len(ids) > 0 && len(names) > 0 {
return db.Where("id IN (?)", ids).Or("name IN (?)", names)
} else if len(ids) > 0 {
return db.Where("id IN (?)", ids)
}
return db.Where("name IN (?)", names)
}
// Host helpers
func ParseInputURL(input string) (*url.URL, error) {
if !strings.Contains(input, "://") {
input = "ssh://" + input
}
u, err := url.Parse(input)
if err != nil {
return nil, err
}
return u, nil
}
func (host *Host) DialAddr() string {
return fmt.Sprintf("%s:%d", host.Hostname(), host.Port())
}
@@ -289,7 +287,7 @@ func HostsPreload(db *gorm.DB) *gorm.DB {
return db.Preload("Groups").Preload("SSHKey")
}
func HostsByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
return GenericNameOrID(db, identifiers)
}
func HostByName(db *gorm.DB, name string) (*Host, error) {
var host Host
@@ -301,7 +299,7 @@ func HostByName(db *gorm.DB, name string) (*Host, error) {
return &host, nil
}
func (host *Host) clientConfig(hk gossh.HostKeyCallback) (*gossh.ClientConfig, error) {
func (host *Host) ClientConfig(hk gossh.HostKeyCallback) (*gossh.ClientConfig, error) {
config := gossh.ClientConfig{
User: host.Username(),
HostKeyCallback: hk,
@@ -329,7 +327,7 @@ func SSHKeysPreload(db *gorm.DB) *gorm.DB {
return db.Preload("Hosts")
}
func SSHKeysByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
return GenericNameOrID(db, identifiers)
}
// HostGroup helpers
@@ -338,7 +336,7 @@ func HostGroupsPreload(db *gorm.DB) *gorm.DB {
return db.Preload("ACLs").Preload("Hosts")
}
func HostGroupsByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
return GenericNameOrID(db, identifiers)
}
// UserGroup helpers
@@ -347,7 +345,7 @@ func UserGroupsPreload(db *gorm.DB) *gorm.DB {
return db.Preload("ACLs").Preload("Users")
}
func UserGroupsByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
return GenericNameOrID(db, identifiers)
}
// User helpers
@@ -356,7 +354,21 @@ func UsersPreload(db *gorm.DB) *gorm.DB {
return db.Preload("Groups").Preload("Keys").Preload("Roles")
}
func UsersByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
return db.Where("id IN (?)", identifiers).Or("email IN (?)", identifiers).Or("name IN (?)", identifiers)
var ids []string
var names []string
for _, s := range identifiers {
if _, err := strconv.Atoi(s); err == nil {
ids = append(ids, s)
} else {
names = append(names, s)
}
}
if len(ids) > 0 && len(names) > 0 {
db.Where("id IN (?)", identifiers).Or("email IN (?)", identifiers).Or("name IN (?)", identifiers)
} else if len(ids) > 0 {
return db.Where("id IN (?)", ids)
}
return db.Where("email IN (?)", identifiers).Or("name IN (?)", identifiers)
}
func (u *User) HasRole(name string) bool {
for _, role := range u.Roles {
@@ -392,14 +404,14 @@ func UserKeysPreload(db *gorm.DB) *gorm.DB {
func UserKeysByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
return db.Where("id IN (?)", identifiers)
}
func UserKeysByUserID(db *gorm.DB, identifiers []string) *gorm.DB {
return db.Where("user_id IN (?)", identifiers)
}
// UserRole helpers
//func UserRolesPreload(db *gorm.DB) *gorm.DB {
// return db.Preload("Users")
//}
func UserRolesByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
return GenericNameOrID(db, identifiers)
}
// Session helpers
@@ -446,7 +458,6 @@ func (e *Event) Log(db *gorm.DB) {
}
func (e *Event) SetAuthor(user *User) *Event {
//e.Author = user
e.AuthorID = user.ID
return e
}

33
pkg/dbmodels/validator.go Normal file
View File

@@ -0,0 +1,33 @@
package dbmodels
import (
"regexp"
"github.com/asaskevich/govalidator"
)
func InitValidator() {
unixUserRegexp := regexp.MustCompile("[a-z_][a-z0-9_-]*")
govalidator.CustomTypeTagMap.Set("unix_user", govalidator.CustomTypeValidator(func(i interface{}, context interface{}) bool {
name, ok := i.(string)
if !ok {
return false
}
return unixUserRegexp.MatchString(name)
}))
govalidator.CustomTypeTagMap.Set("host_logging_mode", govalidator.CustomTypeValidator(func(i interface{}, context interface{}) bool {
name, ok := i.(string)
if !ok {
return false
}
if name == "" {
return true
}
return IsValidHostLoggingMode(name)
}))
}
func IsValidHostLoggingMode(name string) bool {
return name == "disabled" || name == "input" || name == "everything"
}

View File

@@ -0,0 +1,13 @@
package utils
import "regexp"
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
// ValidateEmail validates email.
func ValidateEmail(e string) bool {
if len(e) < 3 || len(e) > 254 {
return false
}
return emailRegex.MatchString(e)
}

View File

@@ -0,0 +1,30 @@
package utils_test
import (
"testing"
"moul.io/sshportal/pkg/utils"
)
func TestValidateEmail(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"goodemail@email.com", true},
{"b@2323.22", true},
{"b@2322.", false},
{"", false},
{"blah", false},
{"blah.com", false},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
got := utils.ValidateEmail(test.input)
if got != test.expected {
t.Errorf("expected %v, got %v", test.expected, got)
}
})
}
}

View File

@@ -1,5 +0,0 @@
{
"extends": [
"config:base"
]
}

361
rules.mk vendored Normal file
View File

@@ -0,0 +1,361 @@
# +--------------------------------------------------------------+
# | * * * moul.io/rules.mk |
# +--------------------------------------------------------------+
# | |
# | ++ ______________________________________ |
# | ++++ / \ |
# | ++++ | | |
# | ++++++++++ | https://moul.io/rules.mk is a set | |
# | +++ | | of common Makefile rules that can | |
# | ++ | | be configured from the Makefile | |
# | + -== ==| | or with environment variables. | |
# | ( <*> <*> | | |
# | | | /| Manfred Touron | |
# | | _) / | manfred.life | |
# | | +++ / \______________________________________/ |
# | \ =+ / |
# | \ + |
# | |\++++++ |
# | | ++++ ||// |
# | ___| |___ _||/__ __|
# | / --- \ \| ||| __ _ ___ __ __/ /|
# |/ | | \ \ / / ' \/ _ \/ // / / |
# || | | | | | /_/_/_/\___/\_,_/_/ |
# +--------------------------------------------------------------+
.PHONY: _default_entrypoint
_default_entrypoint: help
##
## Common helpers
##
rwildcard = $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
check-program = $(foreach exec,$(1),$(if $(shell PATH="$(PATH)" which $(exec)),,$(error "No $(exec) in PATH")))
my-filter-out = $(foreach v,$(2),$(if $(findstring $(1),$(v)),,$(v)))
novendor = $(call my-filter-out,vendor/,$(1))
##
## rules.mk
##
ifneq ($(wildcard rules.mk),)
.PHONY: rulesmk.bumpdeps
rulesmk.bumpdeps:
wget -O rules.mk https://raw.githubusercontent.com/moul/rules.mk/master/rules.mk
BUMPDEPS_STEPS += rulesmk.bumpdeps
endif
##
## Maintainer
##
ifneq ($(wildcard .git/HEAD),)
.PHONY: generate.authors
generate.authors: AUTHORS
AUTHORS: .git/
echo "# This file lists all individuals having contributed content to the repository." > AUTHORS
echo "# For how it is generated, see 'https://github.com/moul/rules.mk'" >> AUTHORS
echo >> AUTHORS
git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf >> AUTHORS
GENERATE_STEPS += generate.authors
endif
##
## Golang
##
ifndef GOPKG
ifneq ($(wildcard go.mod),)
GOPKG = $(shell sed '/module/!d;s/^omdule\ //' go.mod)
endif
endif
ifdef GOPKG
GO ?= go
GOPATH ?= $(HOME)/go
GO_INSTALL_OPTS ?=
GO_TEST_OPTS ?= -test.timeout=30s
GOMOD_DIRS ?= $(sort $(call novendor,$(dir $(call rwildcard,*,*/go.mod go.mod))))
GOCOVERAGE_FILE ?= ./coverage.txt
GOTESTJSON_FILE ?= ./go-test.json
GOBUILDLOG_FILE ?= ./go-build.log
GOINSTALLLOG_FILE ?= ./go-install.log
ifdef GOBINS
.PHONY: go.install
go.install:
ifeq ($(CI),true)
@rm -f /tmp/goinstall.log
@set -e; for dir in $(GOBINS); do ( set -xe; \
cd $$dir; \
$(GO) install -v $(GO_INSTALL_OPTS) .; \
); done 2>&1 | tee $(GOINSTALLLOG_FILE)
else
@set -e; for dir in $(GOBINS); do ( set -xe; \
cd $$dir; \
$(GO) install $(GO_INSTALL_OPTS) .; \
); done
endif
INSTALL_STEPS += go.install
.PHONY: go.release
go.release:
$(call check-program, goreleaser)
goreleaser --snapshot --skip-publish --rm-dist
@echo -n "Do you want to release? [y/N] " && read ans && \
if [ $${ans:-N} = y ]; then set -xe; goreleaser --rm-dist; fi
RELEASE_STEPS += go.release
endif
.PHONY: go.unittest
go.unittest:
ifeq ($(CI),true)
@echo "mode: atomic" > /tmp/gocoverage
@rm -f $(GOTESTJSON_FILE)
@set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -euf pipefail; \
cd $$dir; \
(($(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race -json && touch $@.ok) | tee -a $(GOTESTJSON_FILE) 3>&1 1>&2 2>&3 | tee -a $(GOBUILDLOG_FILE); \
); \
rm $@.ok 2>/dev/null || exit 1; \
if [ -f /tmp/profile.out ]; then \
cat /tmp/profile.out | sed "/mode: atomic/d" >> /tmp/gocoverage; \
rm -f /tmp/profile.out; \
fi)); done
@mv /tmp/gocoverage $(GOCOVERAGE_FILE)
else
@echo "mode: atomic" > /tmp/gocoverage
@set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -xe; \
cd $$dir; \
$(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race); \
if [ -f /tmp/profile.out ]; then \
cat /tmp/profile.out | sed "/mode: atomic/d" >> /tmp/gocoverage; \
rm -f /tmp/profile.out; \
fi); done
@mv /tmp/gocoverage $(GOCOVERAGE_FILE)
endif
.PHONY: go.checkdoc
go.checkdoc:
go doc $(first $(GOMOD_DIRS))
.PHONY: go.coverfunc
go.coverfunc: go.unittest
go tool cover -func=$(GOCOVERAGE_FILE) | grep -v .pb.go: | grep -v .pb.gw.go:
.PHONY: go.lint
go.lint:
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
cd $$dir; \
golangci-lint run --verbose ./...; \
); done
.PHONY: go.tidy
go.tidy:
@# tidy dirs with go.mod files
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
cd $$dir; \
$(GO) mod tidy; \
); done
.PHONY: go.depaware-update
go.depaware-update: go.tidy
@# gen depaware for bins
@set -e; for dir in $(GOBINS); do ( set -xe; \
cd $$dir; \
$(GO) run github.com/tailscale/depaware --update .; \
); done
@# tidy unused depaware deps if not in a tools_test.go file
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
cd $$dir; \
$(GO) mod tidy; \
); done
.PHONY: go.depaware-check
go.depaware-check: go.tidy
@# gen depaware for bins
@set -e; for dir in $(GOBINS); do ( set -xe; \
cd $$dir; \
$(GO) run github.com/tailscale/depaware --check .; \
); done
.PHONY: go.build
go.build:
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
cd $$dir; \
$(GO) build ./...; \
); done
.PHONY: go.bump-deps
go.bumpdeps:
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
cd $$dir; \
$(GO) get -u ./...; \
); done
.PHONY: go.fmt
go.fmt:
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
cd $$dir; \
$(GO) run golang.org/x/tools/cmd/goimports -w `go list -f '{{.Dir}}' ./...` \
); done
VERIFY_STEPS += go.depaware-check
BUILD_STEPS += go.build
BUMPDEPS_STEPS += go.bumpdeps go.depaware-update
TIDY_STEPS += go.tidy
LINT_STEPS += go.lint
UNITTEST_STEPS += go.unittest
FMT_STEPS += go.fmt
# FIXME: disabled, because currently slow
# new rule that is manually run sometimes, i.e. `make pre-release` or `make maintenance`.
# alternative: run it each time the go.mod is changed
#GENERATE_STEPS += go.depaware-update
endif
##
## Gitattributes
##
ifneq ($(wildcard .gitattributes),)
.PHONY: _linguist-ignored
_linguist-kept:
@git check-attr linguist-vendored $(shell git check-attr linguist-generated $(shell find . -type f | grep -v .git/) | grep unspecified | cut -d: -f1) | grep unspecified | cut -d: -f1 | sort
.PHONY: _linguist-kept
_linguist-ignored:
@git check-attr linguist-vendored linguist-ignored `find . -not -path './.git/*' -type f` | grep '\ set$$' | cut -d: -f1 | sort -u
endif
##
## Node
##
ifndef NPM_PACKAGES
ifneq ($(wildcard package.json),)
NPM_PACKAGES = .
endif
endif
ifdef NPM_PACKAGES
.PHONY: npm.publish
npm.publish:
@echo -n "Do you want to npm publish? [y/N] " && read ans && \
@if [ $${ans:-N} = y ]; then \
set -e; for dir in $(NPM_PACKAGES); do ( set -xe; \
cd $$dir; \
npm publish --access=public; \
); done; \
fi
RELEASE_STEPS += npm.publish
endif
##
## Docker
##
docker_build = docker build \
--build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg VERSION=`git describe --tags --always` \
-t "$2" -f "$1" "$(dir $1)"
ifndef DOCKERFILE_PATH
DOCKERFILE_PATH = ./Dockerfile
endif
ifndef DOCKER_IMAGE
ifneq ($(wildcard Dockerfile),)
DOCKER_IMAGE = $(notdir $(PWD))
endif
endif
ifdef DOCKER_IMAGE
ifneq ($(DOCKER_IMAGE),none)
.PHONY: docker.build
docker.build:
$(call check-program, docker)
$(call docker_build,$(DOCKERFILE_PATH),$(DOCKER_IMAGE))
BUILD_STEPS += docker.build
endif
endif
##
## Common
##
TEST_STEPS += $(UNITTEST_STEPS)
TEST_STEPS += $(LINT_STEPS)
TEST_STEPS += $(TIDY_STEPS)
ifneq ($(strip $(TEST_STEPS)),)
.PHONY: test
test: $(PRE_TEST_STEPS) $(TEST_STEPS)
endif
ifdef INSTALL_STEPS
.PHONY: install
install: $(PRE_INSTALL_STEPS) $(INSTALL_STEPS)
endif
ifdef UNITTEST_STEPS
.PHONY: unittest
unittest: $(PRE_UNITTEST_STEPS) $(UNITTEST_STEPS)
endif
ifdef LINT_STEPS
.PHONY: lint
lint: $(PRE_LINT_STEPS) $(FMT_STEPS) $(LINT_STEPS)
endif
ifdef TIDY_STEPS
.PHONY: tidy
tidy: $(PRE_TIDY_STEPS) $(TIDY_STEPS)
endif
ifdef BUILD_STEPS
.PHONY: build
build: $(PRE_BUILD_STEPS) $(BUILD_STEPS)
endif
ifdef VERIFY_STEPS
.PHONY: verify
verify: $(PRE_VERIFY_STEPS) $(VERIFY_STEPS)
endif
ifdef RELEASE_STEPS
.PHONY: release
release: $(PRE_RELEASE_STEPS) $(RELEASE_STEPS)
endif
ifdef BUMPDEPS_STEPS
.PHONY: bumpdeps
bumpdeps: $(PRE_BUMDEPS_STEPS) $(BUMPDEPS_STEPS)
endif
ifdef FMT_STEPS
.PHONY: fmt
fmt: $(PRE_FMT_STEPS) $(FMT_STEPS)
endif
ifdef GENERATE_STEPS
.PHONY: generate
generate: $(PRE_GENERATE_STEPS) $(GENERATE_STEPS)
endif
.PHONY: help
help::
@echo "General commands:"
@[ "$(BUILD_STEPS)" != "" ] && echo " build" || true
@[ "$(BUMPDEPS_STEPS)" != "" ] && echo " bumpdeps" || true
@[ "$(FMT_STEPS)" != "" ] && echo " fmt" || true
@[ "$(GENERATE_STEPS)" != "" ] && echo " generate" || true
@[ "$(INSTALL_STEPS)" != "" ] && echo " install" || true
@[ "$(LINT_STEPS)" != "" ] && echo " lint" || true
@[ "$(RELEASE_STEPS)" != "" ] && echo " release" || true
@[ "$(TEST_STEPS)" != "" ] && echo " test" || true
@[ "$(TIDY_STEPS)" != "" ] && echo " tidy" || true
@[ "$(UNITTEST_STEPS)" != "" ] && echo " unittest" || true
@[ "$(VERIFY_STEPS)" != "" ] && echo " verify" || true
@# FIXME: list other commands
print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true

136
server.go Normal file
View File

@@ -0,0 +1,136 @@
package main
import (
"fmt"
"log"
"math"
"net"
"os"
"time"
"github.com/gliderlabs/ssh"
"github.com/jinzhu/gorm"
"github.com/urfave/cli"
gossh "golang.org/x/crypto/ssh"
"moul.io/sshportal/pkg/bastion"
)
type serverConfig struct {
aesKey string
dbDriver, dbURL string
logsLocation string
bindAddr string
debug, demo bool
idleTimeout time.Duration
aclCheckCmd string
}
func parseServerConfig(c *cli.Context) (*serverConfig, error) {
ret := &serverConfig{
aesKey: c.String("aes-key"),
dbDriver: c.String("db-driver"),
dbURL: c.String("db-conn"),
bindAddr: c.String("bind-address"),
debug: c.Bool("debug"),
demo: c.Bool("demo"),
logsLocation: c.String("logs-location"),
idleTimeout: c.Duration("idle-timeout"),
aclCheckCmd: c.String("acl-check-cmd"),
}
switch len(ret.aesKey) {
case 0, 16, 24, 32:
default:
return nil, fmt.Errorf("invalid aes key size, should be 16 or 24, 32")
}
return ret, nil
}
func ensureLogDirectory(location string) error {
// check for the logdir existence
logsLocation, err := os.Stat(location)
if err != nil {
if os.IsNotExist(err) {
return os.MkdirAll(location, os.ModeDir|os.FileMode(0750))
}
return err
}
if !logsLocation.IsDir() {
return fmt.Errorf("log directory cannot be created")
}
return nil
}
func server(c *serverConfig) (err error) {
var db *gorm.DB
// try to setup the local DB
if db, err = gorm.Open(c.dbDriver, c.dbURL); err != nil {
return
}
defer func() {
origErr := err
err = db.Close()
if origErr != nil {
err = origErr
}
}()
if err = db.DB().Ping(); err != nil {
return
}
db.LogMode(c.debug)
if err = bastion.DBInit(db); err != nil {
return
}
// create TCP listening socket
ln, err := net.Listen("tcp", c.bindAddr)
if err != nil {
return err
}
// configure server
srv := &ssh.Server{
Addr: c.bindAddr,
Handler: func(s ssh.Session) { bastion.ShellHandler(s, GitTag, GitSha, GitTag) }, // ssh.Server.Handler is the handler for the DefaultSessionHandler
Version: fmt.Sprintf("sshportal-%s", GitTag),
ChannelHandlers: map[string]ssh.ChannelHandler{
"default": bastion.ChannelHandler,
},
}
// configure channel handler
bastion.DefaultChannelHandler = func(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
switch newChan.ChannelType() {
case "session":
go ssh.DefaultSessionHandler(srv, conn, newChan, ctx)
case "direct-tcpip":
go ssh.DirectTCPIPHandler(srv, conn, newChan, ctx)
default:
if err := newChan.Reject(gossh.UnknownChannelType, "unsupported channel type"); err != nil {
log.Printf("failed to reject chan: %v", err)
}
}
}
if c.idleTimeout != 0 {
srv.IdleTimeout = c.idleTimeout
// gliderlabs/ssh requires MaxTimeout to be non-zero if we want to use IdleTimeout.
// So, set it to the max value, because we don't want a max timeout.
srv.MaxTimeout = math.MaxInt64
}
for _, opt := range []ssh.Option{
// custom PublicKeyAuth handler
ssh.PublicKeyAuth(bastion.PublicKeyAuthHandler(db, c.logsLocation, c.aclCheckCmd, c.aesKey, c.dbDriver, c.dbURL, c.bindAddr, c.demo)),
ssh.PasswordAuth(bastion.PasswordAuthHandler(db, c.logsLocation, c.aclCheckCmd, c.aesKey, c.dbDriver, c.dbURL, c.bindAddr, c.demo)),
// retrieve sshportal SSH private key from database
bastion.PrivateKeyFromDB(db, c.aesKey),
} {
if err := srv.SetOption(opt); err != nil {
return err
}
}
log.Printf("info: SSH Server accepting connections on %s, idle-timout=%v", c.bindAddr, c.idleTimeout)
return srv.Serve(ln)
}

View File

@@ -1,3 +1,5 @@
// +build !windows
package main
import (
@@ -9,8 +11,8 @@ import (
"syscall"
"unsafe"
"github.com/gliderlabs/ssh"
"github.com/kr/pty"
"github.com/moul/ssh"
"github.com/urfave/cli"
)
@@ -58,7 +60,7 @@ func testServer(c *cli.Context) error {
_, _ = io.Copy(s, f) // #nosec
cmdErr = cmd.Wait()
} else {
//cmd.Stdin = s
// cmd.Stdin = s
cmd.Stdout = s
cmd.Stderr = s
cmdErr = cmd.Run()

14
testserver_unsupported.go Normal file
View File

@@ -0,0 +1,14 @@
// +build windows
package main
import (
"fmt"
"github.com/urfave/cli"
)
// testServer is an hidden handler used for integration tests
func testServer(c *cli.Context) error {
return fmt.Errorf("not available on windows")
}

20
util.go
View File

@@ -1,20 +0,0 @@
package main
import "math/rand"
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func randStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
func wrapText(in string, length int) string {
if len(in) <= length {
return in
}
return in[0:length-3] + "..."
}