From 6c6607ae6873917e2a913d3a17a5d1a2afd993cc Mon Sep 17 00:00:00 2001 From: Guy Lewin Date: Sat, 29 Feb 2020 21:57:01 -0500 Subject: [PATCH] feat: add multiple authentication methods, token and oidc. token is the current token comparison, and oidc generates oidc token using client-credentials flow. in addition - add ping verification using the same method --- README.md | 40 +- client/control.go | 29 +- client/service.go | 33 +- cmd/frpc/sub/root.go | 6 +- cmd/frps/root.go | 6 +- go.mod | 4 + go.sum | 30 + models/auth/auth.go | 151 + models/auth/oidc.go | 255 + models/auth/token.go | 120 + models/config/client_common.go | 14 +- models/config/server_common.go | 12 +- models/consts/consts.go | 4 + models/msg/msg.go | 8 +- server/control.go | 18 +- server/proxy/proxy.go | 1 + server/service.go | 29 +- tests/ci/auth_test.go | 72 + vendor/github.com/coreos/go-oidc/.gitignore | 2 + vendor/github.com/coreos/go-oidc/.travis.yml | 16 + .../github.com/coreos/go-oidc/CONTRIBUTING.md | 71 + vendor/github.com/coreos/go-oidc/DCO | 36 + vendor/github.com/coreos/go-oidc/LICENSE | 202 + vendor/github.com/coreos/go-oidc/MAINTAINERS | 3 + vendor/github.com/coreos/go-oidc/NOTICE | 5 + vendor/github.com/coreos/go-oidc/README.md | 72 + .../coreos/go-oidc/code-of-conduct.md | 61 + vendor/github.com/coreos/go-oidc/jose.go | 20 + vendor/github.com/coreos/go-oidc/jwks.go | 228 + vendor/github.com/coreos/go-oidc/oidc.go | 409 ++ vendor/github.com/coreos/go-oidc/test | 16 + vendor/github.com/coreos/go-oidc/verify.go | 336 ++ vendor/github.com/golang/protobuf/AUTHORS | 3 + .../github.com/golang/protobuf/CONTRIBUTORS | 3 + vendor/github.com/golang/protobuf/LICENSE | 28 + .../github.com/golang/protobuf/proto/clone.go | 253 + .../golang/protobuf/proto/decode.go | 428 ++ .../golang/protobuf/proto/discard.go | 350 ++ .../golang/protobuf/proto/encode.go | 203 + .../github.com/golang/protobuf/proto/equal.go | 300 ++ .../golang/protobuf/proto/extensions.go | 543 ++ .../github.com/golang/protobuf/proto/lib.go | 979 ++++ .../golang/protobuf/proto/message_set.go | 314 ++ .../golang/protobuf/proto/pointer_reflect.go | 357 ++ .../golang/protobuf/proto/pointer_unsafe.go | 308 ++ .../golang/protobuf/proto/properties.go | 544 ++ .../golang/protobuf/proto/table_marshal.go | 2767 +++++++++++ .../golang/protobuf/proto/table_merge.go | 654 +++ .../golang/protobuf/proto/table_unmarshal.go | 2051 ++++++++ .../github.com/golang/protobuf/proto/text.go | 843 ++++ .../golang/protobuf/proto/text_parser.go | 880 ++++ .../github.com/klauspost/cpuid/private-gen.go | 476 ++ .../klauspost/reedsolomon/gentables.go | 132 + .../pquerna/cachecontrol/.travis.yml | 10 + .../github.com/pquerna/cachecontrol/LICENSE | 202 + .../github.com/pquerna/cachecontrol/README.md | 107 + vendor/github.com/pquerna/cachecontrol/api.go | 48 + .../cachecontrol/cacheobject/directive.go | 546 +++ .../pquerna/cachecontrol/cacheobject/lex.go | 93 + .../cachecontrol/cacheobject/object.go | 387 ++ .../cachecontrol/cacheobject/reasons.go | 95 + .../cachecontrol/cacheobject/warning.go | 107 + vendor/github.com/pquerna/cachecontrol/doc.go | 25 + vendor/github.com/tjfoc/gmsm/sm4/key.pem | 3 - vendor/golang.org/x/crypto/ed25519/ed25519.go | 217 + .../ed25519/internal/edwards25519/const.go | 1422 ++++++ .../internal/edwards25519/edwards25519.go | 1793 +++++++ .../x/net/context/ctxhttp/ctxhttp.go | 71 + vendor/golang.org/x/net/internal/iana/gen.go | 383 ++ .../x/net/internal/socket/defs_aix.go | 39 + .../x/net/internal/socket/defs_darwin.go | 36 + .../x/net/internal/socket/defs_dragonfly.go | 36 + .../x/net/internal/socket/defs_freebsd.go | 36 + .../x/net/internal/socket/defs_linux.go | 41 + .../x/net/internal/socket/defs_netbsd.go | 39 + .../x/net/internal/socket/defs_openbsd.go | 36 + .../x/net/internal/socket/defs_solaris.go | 36 + vendor/golang.org/x/net/ipv4/defs_aix.go | 39 + vendor/golang.org/x/net/ipv4/defs_darwin.go | 77 + .../golang.org/x/net/ipv4/defs_dragonfly.go | 38 + vendor/golang.org/x/net/ipv4/defs_freebsd.go | 75 + vendor/golang.org/x/net/ipv4/defs_linux.go | 122 + vendor/golang.org/x/net/ipv4/defs_netbsd.go | 37 + vendor/golang.org/x/net/ipv4/defs_openbsd.go | 37 + vendor/golang.org/x/net/ipv4/defs_solaris.go | 84 + vendor/golang.org/x/net/ipv4/gen.go | 199 + vendor/golang.org/x/net/ipv6/defs_aix.go | 82 + vendor/golang.org/x/net/ipv6/defs_darwin.go | 112 + .../golang.org/x/net/ipv6/defs_dragonfly.go | 84 + vendor/golang.org/x/net/ipv6/defs_freebsd.go | 105 + vendor/golang.org/x/net/ipv6/defs_linux.go | 147 + vendor/golang.org/x/net/ipv6/defs_netbsd.go | 80 + vendor/golang.org/x/net/ipv6/defs_openbsd.go | 89 + vendor/golang.org/x/net/ipv6/defs_solaris.go | 114 + vendor/golang.org/x/net/ipv6/gen.go | 199 + vendor/golang.org/x/oauth2/.travis.yml | 13 + vendor/golang.org/x/oauth2/AUTHORS | 3 + vendor/golang.org/x/oauth2/CONTRIBUTING.md | 26 + vendor/golang.org/x/oauth2/CONTRIBUTORS | 3 + vendor/golang.org/x/oauth2/LICENSE | 27 + vendor/golang.org/x/oauth2/README.md | 36 + .../clientcredentials/clientcredentials.go | 120 + vendor/golang.org/x/oauth2/go.mod | 10 + vendor/golang.org/x/oauth2/go.sum | 12 + .../x/oauth2/internal/client_appengine.go | 13 + vendor/golang.org/x/oauth2/internal/doc.go | 6 + vendor/golang.org/x/oauth2/internal/oauth2.go | 37 + vendor/golang.org/x/oauth2/internal/token.go | 294 ++ .../golang.org/x/oauth2/internal/transport.go | 33 + vendor/golang.org/x/oauth2/oauth2.go | 381 ++ vendor/golang.org/x/oauth2/token.go | 178 + vendor/golang.org/x/oauth2/transport.go | 89 + vendor/golang.org/x/sys/unix/mkasm_darwin.go | 61 + vendor/golang.org/x/sys/unix/mkpost.go | 106 + vendor/golang.org/x/sys/unix/mksyscall.go | 402 ++ .../x/sys/unix/mksyscall_aix_ppc.go | 404 ++ .../x/sys/unix/mksyscall_aix_ppc64.go | 602 +++ .../x/sys/unix/mksyscall_solaris.go | 335 ++ vendor/golang.org/x/sys/unix/mksysnum.go | 190 + vendor/golang.org/x/sys/unix/types_aix.go | 236 + vendor/golang.org/x/sys/unix/types_darwin.go | 277 ++ .../golang.org/x/sys/unix/types_dragonfly.go | 263 + vendor/golang.org/x/sys/unix/types_freebsd.go | 356 ++ vendor/golang.org/x/sys/unix/types_netbsd.go | 289 ++ vendor/golang.org/x/sys/unix/types_openbsd.go | 276 ++ vendor/golang.org/x/sys/unix/types_solaris.go | 266 + vendor/golang.org/x/text/unicode/bidi/gen.go | 133 + .../x/text/unicode/bidi/gen_ranges.go | 57 + .../x/text/unicode/bidi/gen_trieval.go | 64 + .../x/text/unicode/norm/maketables.go | 986 ++++ .../golang.org/x/text/unicode/norm/triegen.go | 117 + vendor/google.golang.org/appengine/LICENSE | 202 + .../appengine/internal/api.go | 671 +++ .../appengine/internal/api_classic.go | 169 + .../appengine/internal/api_common.go | 123 + .../appengine/internal/app_id.go | 28 + .../appengine/internal/base/api_base.pb.go | 308 ++ .../appengine/internal/base/api_base.proto | 33 + .../internal/datastore/datastore_v3.pb.go | 4367 +++++++++++++++++ .../internal/datastore/datastore_v3.proto | 551 +++ .../appengine/internal/identity.go | 55 + .../appengine/internal/identity_classic.go | 61 + .../appengine/internal/identity_flex.go | 11 + .../appengine/internal/identity_vm.go | 134 + .../appengine/internal/internal.go | 110 + .../appengine/internal/log/log_service.pb.go | 1313 +++++ .../appengine/internal/log/log_service.proto | 150 + .../appengine/internal/main.go | 16 + .../appengine/internal/main_common.go | 7 + .../appengine/internal/main_vm.go | 69 + .../appengine/internal/metadata.go | 60 + .../appengine/internal/net.go | 56 + .../appengine/internal/regen.sh | 40 + .../internal/remote_api/remote_api.pb.go | 361 ++ .../internal/remote_api/remote_api.proto | 44 + .../appengine/internal/transaction.go | 115 + .../internal/urlfetch/urlfetch_service.pb.go | 527 ++ .../internal/urlfetch/urlfetch_service.proto | 64 + .../appengine/urlfetch/urlfetch.go | 210 + .../square/go-jose.v2/.gitcookies.sh.enc | 1 + vendor/gopkg.in/square/go-jose.v2/.gitignore | 7 + vendor/gopkg.in/square/go-jose.v2/.travis.yml | 44 + .../gopkg.in/square/go-jose.v2/BUG-BOUNTY.md | 10 + .../square/go-jose.v2/CONTRIBUTING.md | 14 + vendor/gopkg.in/square/go-jose.v2/LICENSE | 202 + vendor/gopkg.in/square/go-jose.v2/README.md | 118 + .../gopkg.in/square/go-jose.v2/asymmetric.go | 592 +++ .../square/go-jose.v2/cipher/cbc_hmac.go | 196 + .../square/go-jose.v2/cipher/concat_kdf.go | 75 + .../square/go-jose.v2/cipher/ecdh_es.go | 86 + .../square/go-jose.v2/cipher/key_wrap.go | 109 + vendor/gopkg.in/square/go-jose.v2/crypter.go | 541 ++ vendor/gopkg.in/square/go-jose.v2/doc.go | 27 + vendor/gopkg.in/square/go-jose.v2/encoding.go | 185 + .../gopkg.in/square/go-jose.v2/json/LICENSE | 27 + .../gopkg.in/square/go-jose.v2/json/README.md | 13 + .../gopkg.in/square/go-jose.v2/json/decode.go | 1183 +++++ .../gopkg.in/square/go-jose.v2/json/encode.go | 1197 +++++ .../gopkg.in/square/go-jose.v2/json/indent.go | 141 + .../square/go-jose.v2/json/scanner.go | 623 +++ .../gopkg.in/square/go-jose.v2/json/stream.go | 480 ++ .../gopkg.in/square/go-jose.v2/json/tags.go | 44 + vendor/gopkg.in/square/go-jose.v2/jwe.go | 294 ++ vendor/gopkg.in/square/go-jose.v2/jwk.go | 608 +++ vendor/gopkg.in/square/go-jose.v2/jws.go | 366 ++ vendor/gopkg.in/square/go-jose.v2/opaque.go | 144 + vendor/gopkg.in/square/go-jose.v2/shared.go | 520 ++ vendor/gopkg.in/square/go-jose.v2/signing.go | 441 ++ .../gopkg.in/square/go-jose.v2/symmetric.go | 482 ++ vendor/modules.txt | 54 +- 190 files changed, 47571 insertions(+), 62 deletions(-) create mode 100644 models/auth/auth.go create mode 100644 models/auth/oidc.go create mode 100644 models/auth/token.go create mode 100644 tests/ci/auth_test.go create mode 100644 vendor/github.com/coreos/go-oidc/.gitignore create mode 100644 vendor/github.com/coreos/go-oidc/.travis.yml create mode 100644 vendor/github.com/coreos/go-oidc/CONTRIBUTING.md create mode 100644 vendor/github.com/coreos/go-oidc/DCO create mode 100644 vendor/github.com/coreos/go-oidc/LICENSE create mode 100644 vendor/github.com/coreos/go-oidc/MAINTAINERS create mode 100644 vendor/github.com/coreos/go-oidc/NOTICE create mode 100644 vendor/github.com/coreos/go-oidc/README.md create mode 100644 vendor/github.com/coreos/go-oidc/code-of-conduct.md create mode 100644 vendor/github.com/coreos/go-oidc/jose.go create mode 100644 vendor/github.com/coreos/go-oidc/jwks.go create mode 100644 vendor/github.com/coreos/go-oidc/oidc.go create mode 100644 vendor/github.com/coreos/go-oidc/test create mode 100644 vendor/github.com/coreos/go-oidc/verify.go create mode 100644 vendor/github.com/golang/protobuf/AUTHORS create mode 100644 vendor/github.com/golang/protobuf/CONTRIBUTORS create mode 100644 vendor/github.com/golang/protobuf/LICENSE create mode 100644 vendor/github.com/golang/protobuf/proto/clone.go create mode 100644 vendor/github.com/golang/protobuf/proto/decode.go create mode 100644 vendor/github.com/golang/protobuf/proto/discard.go create mode 100644 vendor/github.com/golang/protobuf/proto/encode.go create mode 100644 vendor/github.com/golang/protobuf/proto/equal.go create mode 100644 vendor/github.com/golang/protobuf/proto/extensions.go create mode 100644 vendor/github.com/golang/protobuf/proto/lib.go create mode 100644 vendor/github.com/golang/protobuf/proto/message_set.go create mode 100644 vendor/github.com/golang/protobuf/proto/pointer_reflect.go create mode 100644 vendor/github.com/golang/protobuf/proto/pointer_unsafe.go create mode 100644 vendor/github.com/golang/protobuf/proto/properties.go create mode 100644 vendor/github.com/golang/protobuf/proto/table_marshal.go create mode 100644 vendor/github.com/golang/protobuf/proto/table_merge.go create mode 100644 vendor/github.com/golang/protobuf/proto/table_unmarshal.go create mode 100644 vendor/github.com/golang/protobuf/proto/text.go create mode 100644 vendor/github.com/golang/protobuf/proto/text_parser.go create mode 100644 vendor/github.com/klauspost/cpuid/private-gen.go create mode 100644 vendor/github.com/klauspost/reedsolomon/gentables.go create mode 100644 vendor/github.com/pquerna/cachecontrol/.travis.yml create mode 100644 vendor/github.com/pquerna/cachecontrol/LICENSE create mode 100644 vendor/github.com/pquerna/cachecontrol/README.md create mode 100644 vendor/github.com/pquerna/cachecontrol/api.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/directive.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/lex.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/object.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/reasons.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/warning.go create mode 100644 vendor/github.com/pquerna/cachecontrol/doc.go delete mode 100644 vendor/github.com/tjfoc/gmsm/sm4/key.pem create mode 100644 vendor/golang.org/x/crypto/ed25519/ed25519.go create mode 100644 vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go create mode 100644 vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go create mode 100644 vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go create mode 100644 vendor/golang.org/x/net/internal/iana/gen.go create mode 100644 vendor/golang.org/x/net/internal/socket/defs_aix.go create mode 100644 vendor/golang.org/x/net/internal/socket/defs_darwin.go create mode 100644 vendor/golang.org/x/net/internal/socket/defs_dragonfly.go create mode 100644 vendor/golang.org/x/net/internal/socket/defs_freebsd.go create mode 100644 vendor/golang.org/x/net/internal/socket/defs_linux.go create mode 100644 vendor/golang.org/x/net/internal/socket/defs_netbsd.go create mode 100644 vendor/golang.org/x/net/internal/socket/defs_openbsd.go create mode 100644 vendor/golang.org/x/net/internal/socket/defs_solaris.go create mode 100644 vendor/golang.org/x/net/ipv4/defs_aix.go create mode 100644 vendor/golang.org/x/net/ipv4/defs_darwin.go create mode 100644 vendor/golang.org/x/net/ipv4/defs_dragonfly.go create mode 100644 vendor/golang.org/x/net/ipv4/defs_freebsd.go create mode 100644 vendor/golang.org/x/net/ipv4/defs_linux.go create mode 100644 vendor/golang.org/x/net/ipv4/defs_netbsd.go create mode 100644 vendor/golang.org/x/net/ipv4/defs_openbsd.go create mode 100644 vendor/golang.org/x/net/ipv4/defs_solaris.go create mode 100644 vendor/golang.org/x/net/ipv4/gen.go create mode 100644 vendor/golang.org/x/net/ipv6/defs_aix.go create mode 100644 vendor/golang.org/x/net/ipv6/defs_darwin.go create mode 100644 vendor/golang.org/x/net/ipv6/defs_dragonfly.go create mode 100644 vendor/golang.org/x/net/ipv6/defs_freebsd.go create mode 100644 vendor/golang.org/x/net/ipv6/defs_linux.go create mode 100644 vendor/golang.org/x/net/ipv6/defs_netbsd.go create mode 100644 vendor/golang.org/x/net/ipv6/defs_openbsd.go create mode 100644 vendor/golang.org/x/net/ipv6/defs_solaris.go create mode 100644 vendor/golang.org/x/net/ipv6/gen.go create mode 100644 vendor/golang.org/x/oauth2/.travis.yml create mode 100644 vendor/golang.org/x/oauth2/AUTHORS create mode 100644 vendor/golang.org/x/oauth2/CONTRIBUTING.md create mode 100644 vendor/golang.org/x/oauth2/CONTRIBUTORS create mode 100644 vendor/golang.org/x/oauth2/LICENSE create mode 100644 vendor/golang.org/x/oauth2/README.md create mode 100644 vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go create mode 100644 vendor/golang.org/x/oauth2/go.mod create mode 100644 vendor/golang.org/x/oauth2/go.sum create mode 100644 vendor/golang.org/x/oauth2/internal/client_appengine.go create mode 100644 vendor/golang.org/x/oauth2/internal/doc.go create mode 100644 vendor/golang.org/x/oauth2/internal/oauth2.go create mode 100644 vendor/golang.org/x/oauth2/internal/token.go create mode 100644 vendor/golang.org/x/oauth2/internal/transport.go create mode 100644 vendor/golang.org/x/oauth2/oauth2.go create mode 100644 vendor/golang.org/x/oauth2/token.go create mode 100644 vendor/golang.org/x/oauth2/transport.go create mode 100644 vendor/golang.org/x/sys/unix/mkasm_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/mkpost.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall_solaris.go create mode 100644 vendor/golang.org/x/sys/unix/mksysnum.go create mode 100644 vendor/golang.org/x/sys/unix/types_aix.go create mode 100644 vendor/golang.org/x/sys/unix/types_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/types_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/types_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/types_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/types_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/types_solaris.go create mode 100644 vendor/golang.org/x/text/unicode/bidi/gen.go create mode 100644 vendor/golang.org/x/text/unicode/bidi/gen_ranges.go create mode 100644 vendor/golang.org/x/text/unicode/bidi/gen_trieval.go create mode 100644 vendor/golang.org/x/text/unicode/norm/maketables.go create mode 100644 vendor/golang.org/x/text/unicode/norm/triegen.go create mode 100644 vendor/google.golang.org/appengine/LICENSE create mode 100644 vendor/google.golang.org/appengine/internal/api.go create mode 100644 vendor/google.golang.org/appengine/internal/api_classic.go create mode 100644 vendor/google.golang.org/appengine/internal/api_common.go create mode 100644 vendor/google.golang.org/appengine/internal/app_id.go create mode 100644 vendor/google.golang.org/appengine/internal/base/api_base.pb.go create mode 100644 vendor/google.golang.org/appengine/internal/base/api_base.proto create mode 100644 vendor/google.golang.org/appengine/internal/datastore/datastore_v3.pb.go create mode 100644 vendor/google.golang.org/appengine/internal/datastore/datastore_v3.proto create mode 100644 vendor/google.golang.org/appengine/internal/identity.go create mode 100644 vendor/google.golang.org/appengine/internal/identity_classic.go create mode 100644 vendor/google.golang.org/appengine/internal/identity_flex.go create mode 100644 vendor/google.golang.org/appengine/internal/identity_vm.go create mode 100644 vendor/google.golang.org/appengine/internal/internal.go create mode 100644 vendor/google.golang.org/appengine/internal/log/log_service.pb.go create mode 100644 vendor/google.golang.org/appengine/internal/log/log_service.proto create mode 100644 vendor/google.golang.org/appengine/internal/main.go create mode 100644 vendor/google.golang.org/appengine/internal/main_common.go create mode 100644 vendor/google.golang.org/appengine/internal/main_vm.go create mode 100644 vendor/google.golang.org/appengine/internal/metadata.go create mode 100644 vendor/google.golang.org/appengine/internal/net.go create mode 100644 vendor/google.golang.org/appengine/internal/regen.sh create mode 100644 vendor/google.golang.org/appengine/internal/remote_api/remote_api.pb.go create mode 100644 vendor/google.golang.org/appengine/internal/remote_api/remote_api.proto create mode 100644 vendor/google.golang.org/appengine/internal/transaction.go create mode 100644 vendor/google.golang.org/appengine/internal/urlfetch/urlfetch_service.pb.go create mode 100644 vendor/google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto create mode 100644 vendor/google.golang.org/appengine/urlfetch/urlfetch.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/.gitcookies.sh.enc create mode 100644 vendor/gopkg.in/square/go-jose.v2/.gitignore create mode 100644 vendor/gopkg.in/square/go-jose.v2/.travis.yml create mode 100644 vendor/gopkg.in/square/go-jose.v2/BUG-BOUNTY.md create mode 100644 vendor/gopkg.in/square/go-jose.v2/CONTRIBUTING.md create mode 100644 vendor/gopkg.in/square/go-jose.v2/LICENSE create mode 100644 vendor/gopkg.in/square/go-jose.v2/README.md create mode 100644 vendor/gopkg.in/square/go-jose.v2/asymmetric.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/cipher/cbc_hmac.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/cipher/concat_kdf.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/cipher/ecdh_es.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/cipher/key_wrap.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/crypter.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/doc.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/encoding.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/LICENSE create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/README.md create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/decode.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/encode.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/indent.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/scanner.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/stream.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/tags.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/jwe.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/jwk.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/jws.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/opaque.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/shared.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/signing.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/symmetric.go diff --git a/README.md b/README.md index c65d7fa2..a0bdaa12 100644 --- a/README.md +++ b/README.md @@ -437,7 +437,45 @@ Then visit `http://127.0.0.1:7400` to see admin UI, with username and password b ### Authenticating the Client -Always use the same `token` in the `[common]` section in `frps.ini` and `frpc.ini`. +There are 2 authentication methods to authenticate frpc with frps. + +You can decide which one to use by configuring `authentication_method` under `[common]` in `frpc.ini` and `frps.ini`. + +Configuring `authenticate_heartbeats = true` under `[common]` will use the configured authentication method to add and validate authentication on every heartbeat between frpc and frps. + +Configuring `authenticate_new_work_conns = true` under `[common]` will do the same for every new work connection between frpc and frps. + +#### Token Authentication + +When specifying `authentication_method = token` under `[common]` in `frpc.ini` and `frps.ini` - token based authentication will be used. + +Make sure to specify the same `token` in the `[common]` section in `frps.ini` and `frpc.ini` for frpc to pass frps validation + +#### OIDC Authentication + +When specifying `authentication_method = oidc` under `[common]` in `frpc.ini` and `frps.ini` - OIDC based authentication will be used. + +OIDC stands for OpenID Connect, and the flow used is called [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4). + +To use this authentication type - configure `frpc.ini` and `frps.ini` as follows: + +```ini +# frps.ini +[common] +authentication_method = oidc +oidc_issuer = https://example-oidc-issuer.com/ +oidc_audience = https://oidc-audience.com/.default +``` + +```ini +# frpc.ini +[common] +authentication_method = oidc +oidc_client_id = 98692467-37de-409a-9fac-bb2585826f18 # Replace with OIDC client ID +oidc_client_secret = oidc_secret +oidc_audience = https://oidc-audience.com/.default +oidc_token_endpoint_url = https://example-oidc-endpoint.com/oauth2/v2.0/token +``` ### Encryption and Compression diff --git a/client/control.go b/client/control.go index 5589817f..f415f65f 100644 --- a/client/control.go +++ b/client/control.go @@ -25,6 +25,7 @@ import ( "time" "github.com/fatedier/frp/client/proxy" + "github.com/fatedier/frp/models/auth" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" frpNet "github.com/fatedier/frp/utils/net" @@ -82,13 +83,17 @@ type Control struct { // service context ctx context.Context + + // sets authentication based on selected method + authSetter auth.Setter } func NewControl(ctx context.Context, runId string, conn net.Conn, session *fmux.Session, clientCfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, - serverUDPPort int) *Control { + serverUDPPort int, + authSetter auth.Setter) *Control { // new xlog instance ctl := &Control{ @@ -107,6 +112,7 @@ func NewControl(ctx context.Context, runId string, conn net.Conn, session *fmux. serverUDPPort: serverUDPPort, xl: xlog.FromContextSafe(ctx), ctx: ctx, + authSetter: authSetter, } ctl.pm = proxy.NewProxyManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort) @@ -136,6 +142,10 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) { m := &msg.NewWorkConn{ RunId: ctl.runId, } + if err = ctl.authSetter.SetNewWorkConn(m); err != nil { + xl.Warn("error during NewWorkConn authentication: %v", err) + return + } if err = msg.WriteMsg(workConn, m); err != nil { xl.Warn("work connection write to server error: %v", err) workConn.Close() @@ -148,6 +158,11 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) { workConn.Close() return } + if startMsg.Error != "" { + xl.Error("StartWorkConn contains error: %s", startMsg.Error) + workConn.Close() + return + } // dispatch this work connection to related proxy ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg) @@ -282,7 +297,12 @@ func (ctl *Control) msgHandler() { case <-hbSend.C: // send heartbeat to server xl.Debug("send heartbeat to server") - ctl.sendCh <- &msg.Ping{} + pingMsg := &msg.Ping{} + if err := ctl.authSetter.SetPing(pingMsg); err != nil { + xl.Warn("error during ping authentication: %v", err) + return + } + ctl.sendCh <- pingMsg case <-hbCheck.C: if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second { xl.Warn("heartbeat timeout") @@ -301,6 +321,11 @@ func (ctl *Control) msgHandler() { case *msg.NewProxyResp: ctl.HandleNewProxyResp(m) case *msg.Pong: + if m.Error != "" { + xl.Error("Pong contains error: %s", m.Error) + ctl.conn.Close() + return + } ctl.lastPong = time.Now() xl.Debug("receive heartbeat from server") } diff --git a/client/service.go b/client/service.go index 5ad08855..297d3a36 100644 --- a/client/service.go +++ b/client/service.go @@ -26,11 +26,11 @@ import ( "time" "github.com/fatedier/frp/assets" + "github.com/fatedier/frp/models/auth" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/utils/log" frpNet "github.com/fatedier/frp/utils/net" - "github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/xlog" @@ -46,6 +46,9 @@ type Service struct { ctl *Control ctlMu sync.RWMutex + // Sets authentication based on selected method + authSetter auth.Setter + cfg config.ClientCommonConf pxyCfgs map[string]config.ProxyConf visitorCfgs map[string]config.VisitorConf @@ -70,6 +73,7 @@ func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf ctx, cancel := context.WithCancel(context.Background()) svr = &Service{ + authSetter: auth.NewAuthSetter(cfg.AuthClientConfig), cfg: cfg, cfgFile: cfgFile, pxyCfgs: pxyCfgs, @@ -105,7 +109,7 @@ func (svr *Service) Run() error { } } else { // login success - ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort) + ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter) ctl.Run() svr.ctlMu.Lock() svr.ctl = ctl @@ -159,7 +163,7 @@ func (svr *Service) keepControllerWorking() { // reconnect success, init delayTime delayTime = time.Second - ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort) + ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter) ctl.Run() svr.ctlMu.Lock() svr.ctl = ctl @@ -212,17 +216,20 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) { conn = stream } - now := time.Now().Unix() loginMsg := &msg.Login{ - Arch: runtime.GOARCH, - Os: runtime.GOOS, - PoolCount: svr.cfg.PoolCount, - User: svr.cfg.User, - Version: version.Full(), - PrivilegeKey: util.GetAuthKey(svr.cfg.Token, now), - Timestamp: now, - RunId: svr.runId, - Metas: svr.cfg.Metas, + Arch: runtime.GOARCH, + Os: runtime.GOOS, + PoolCount: svr.cfg.PoolCount, + User: svr.cfg.User, + Version: version.Full(), + Timestamp: time.Now().Unix(), + RunId: svr.runId, + Metas: svr.cfg.Metas, + } + + // Add auth + if err = svr.authSetter.SetLogin(loginMsg); err != nil { + return } if err = msg.WriteMsg(conn, loginMsg); err != nil { diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index 82a906bd..8ef25e48 100644 --- a/cmd/frpc/sub/root.go +++ b/cmd/frpc/sub/root.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/cobra" "github.com/fatedier/frp/client" + "github.com/fatedier/frp/models/auth" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/version" @@ -157,7 +158,6 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) { cfg.User = user cfg.Protocol = protocol - cfg.Token = token cfg.LogLevel = logLevel cfg.LogFile = logFile cfg.LogMaxDays = int64(logMaxDays) @@ -168,6 +168,10 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) { } cfg.DisableLogColor = disableLogColor + // Only token authentication is supported in cmd mode + cfg.AuthClientConfig = auth.GetDefaultAuthClientConf() + cfg.Token = token + return } diff --git a/cmd/frps/root.go b/cmd/frps/root.go index ec175fe3..9096b28c 100644 --- a/cmd/frps/root.go +++ b/cmd/frps/root.go @@ -20,6 +20,7 @@ import ( "github.com/spf13/cobra" + "github.com/fatedier/frp/models/auth" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/server" "github.com/fatedier/frp/utils/log" @@ -171,8 +172,11 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) { cfg.LogFile = logFile cfg.LogLevel = logLevel cfg.LogMaxDays = logMaxDays - cfg.Token = token cfg.SubDomainHost = subDomainHost + + // Only token authentication is supported in cmd mode + cfg.AuthServerConfig = auth.GetDefaultAuthServerConf() + cfg.Token = token if len(allowPorts) > 0 { // e.g. 1000-2000,2001,2002,3000-4000 ports, errRet := util.ParseRangeNumbers(allowPorts) diff --git a/go.mod b/go.mod index a71a97c0..c43c3646 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 + github.com/coreos/go-oidc v2.2.1+incompatible github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible @@ -17,6 +18,7 @@ require ( github.com/mattn/go-runewidth v0.0.4 // indirect github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc github.com/pkg/errors v0.8.0 // indirect + github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/rakyll/statik v0.1.1 github.com/rodaine/table v1.0.0 github.com/spf13/cobra v0.0.3 @@ -28,6 +30,8 @@ require ( github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/text v0.3.2 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + gopkg.in/square/go-jose.v2 v2.4.1 // indirect ) diff --git a/go.sum b/go.sum index 26c9004f..36268cd5 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,19 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= +github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +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/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw= github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk= github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8= github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM= github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74= github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -22,25 +31,42 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8= github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/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/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg= github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY= github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8= github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= +github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk= github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= +github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw= github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= +github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks= github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -49,3 +75,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= +gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= diff --git a/models/auth/auth.go b/models/auth/auth.go new file mode 100644 index 00000000..90a0c160 --- /dev/null +++ b/models/auth/auth.go @@ -0,0 +1,151 @@ +// Copyright 2020 guylewin, guy@lewin.co.il +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "fmt" + + "github.com/fatedier/frp/models/consts" + "github.com/fatedier/frp/models/msg" + + "github.com/vaughan0/go-ini" +) + +type baseConfig struct { + // AuthenticationMethod specifies what authentication method to use to + // authenticate frpc with frps. If "token" is specified - token will be + // read into login message. If "oidc" is specified - OIDC (Open ID Connect) + // token will be issued using OIDC settings. By default, this value is "token". + AuthenticationMethod string `json:"authentication_method"` + // AuthenticateHeartBeats specifies whether to include authentication token in + // heartbeats sent to frps. By default, this value is false. + AuthenticateHeartBeats bool `json:"authenticate_heartbeats"` + // AuthenticateNewWorkConns specifies whether to include authentication token in + // new work connections sent to frps. By default, this value is false. + AuthenticateNewWorkConns bool `json:"authenticate_new_work_conns"` +} + +func getDefaultBaseConf() baseConfig { + return baseConfig{ + AuthenticationMethod: "token", + AuthenticateHeartBeats: false, + AuthenticateNewWorkConns: false, + } +} + +func unmarshalBaseConfFromIni(conf ini.File) baseConfig { + var ( + tmpStr string + ok bool + ) + + cfg := getDefaultBaseConf() + + if tmpStr, ok = conf.Get("common", "authentication_method"); ok { + cfg.AuthenticationMethod = tmpStr + } + + if tmpStr, ok = conf.Get("common", "authenticate_heartbeats"); ok && tmpStr == "true" { + cfg.AuthenticateHeartBeats = true + } else { + cfg.AuthenticateHeartBeats = false + } + + if tmpStr, ok = conf.Get("common", "authenticate_new_work_conns"); ok && tmpStr == "true" { + cfg.AuthenticateNewWorkConns = true + } else { + cfg.AuthenticateNewWorkConns = false + } + + return cfg +} + +type AuthClientConfig struct { + baseConfig + oidcClientConfig + tokenConfig +} + +func GetDefaultAuthClientConf() AuthClientConfig { + return AuthClientConfig{ + baseConfig: getDefaultBaseConf(), + oidcClientConfig: getDefaultOidcClientConf(), + tokenConfig: getDefaultTokenConf(), + } +} + +func UnmarshalAuthClientConfFromIni(conf ini.File) (cfg AuthClientConfig) { + cfg.baseConfig = unmarshalBaseConfFromIni(conf) + cfg.oidcClientConfig = unmarshalOidcClientConfFromIni(conf) + cfg.tokenConfig = unmarshalTokenConfFromIni(conf) + return cfg +} + +type AuthServerConfig struct { + baseConfig + oidcServerConfig + tokenConfig +} + +func GetDefaultAuthServerConf() AuthServerConfig { + return AuthServerConfig{ + baseConfig: getDefaultBaseConf(), + oidcServerConfig: getDefaultOidcServerConf(), + tokenConfig: getDefaultTokenConf(), + } +} + +func UnmarshalAuthServerConfFromIni(conf ini.File) (cfg AuthServerConfig) { + cfg.baseConfig = unmarshalBaseConfFromIni(conf) + cfg.oidcServerConfig = unmarshalOidcServerConfFromIni(conf) + cfg.tokenConfig = unmarshalTokenConfFromIni(conf) + return cfg +} + +type Setter interface { + SetLogin(*msg.Login) error + SetPing(*msg.Ping) error + SetNewWorkConn(*msg.NewWorkConn) error +} + +func NewAuthSetter(cfg AuthClientConfig) (authProvider Setter) { + switch cfg.AuthenticationMethod { + case consts.TokenAuthMethod: + authProvider = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig) + case consts.OidcAuthMethod: + authProvider = NewOidcAuthSetter(cfg.baseConfig, cfg.oidcClientConfig) + default: + panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod)) + } + + return authProvider +} + +type Verifier interface { + VerifyLogin(*msg.Login) error + VerifyPing(*msg.Ping) error + VerifyNewWorkConn(*msg.NewWorkConn) error +} + +func NewAuthVerifier(cfg AuthServerConfig) (authVerifier Verifier) { + switch cfg.AuthenticationMethod { + case consts.TokenAuthMethod: + authVerifier = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig) + case consts.OidcAuthMethod: + authVerifier = NewOidcAuthVerifier(cfg.baseConfig, cfg.oidcServerConfig) + } + + return authVerifier +} diff --git a/models/auth/oidc.go b/models/auth/oidc.go new file mode 100644 index 00000000..b38b1c08 --- /dev/null +++ b/models/auth/oidc.go @@ -0,0 +1,255 @@ +// Copyright 2020 guylewin, guy@lewin.co.il +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "context" + "fmt" + + "github.com/fatedier/frp/models/msg" + + "github.com/coreos/go-oidc" + "github.com/vaughan0/go-ini" + "golang.org/x/oauth2/clientcredentials" +) + +type oidcClientConfig struct { + // OidcClientId specifies the client ID to use to get a token in OIDC + // authentication if AuthenticationMethod == "oidc". By default, this value + // is "". + OidcClientId string `json:"oidc_client_id"` + // OidcClientSecret specifies the client secret to use to get a token in OIDC + // authentication if AuthenticationMethod == "oidc". By default, this value + // is "". + OidcClientSecret string `json:"oidc_client_secret"` + // OidcAudience specifies the audience of the token in OIDC authentication + //if AuthenticationMethod == "oidc". By default, this value is "". + OidcAudience string `json:"oidc_audience"` + // OidcTokenEndpointUrl specifies the URL which implements OIDC Token Endpoint. + // It will be used to get an OIDC token if AuthenticationMethod == "oidc". + // By default, this value is "". + OidcTokenEndpointUrl string `json:"oidc_token_endpoint_url"` +} + +func getDefaultOidcClientConf() oidcClientConfig { + return oidcClientConfig{ + OidcClientId: "", + OidcClientSecret: "", + OidcAudience: "", + OidcTokenEndpointUrl: "", + } +} + +func unmarshalOidcClientConfFromIni(conf ini.File) oidcClientConfig { + var ( + tmpStr string + ok bool + ) + + cfg := getDefaultOidcClientConf() + + if tmpStr, ok = conf.Get("common", "oidc_client_id"); ok { + cfg.OidcClientId = tmpStr + } + + if tmpStr, ok = conf.Get("common", "oidc_client_secret"); ok { + cfg.OidcClientSecret = tmpStr + } + + if tmpStr, ok = conf.Get("common", "oidc_audience"); ok { + cfg.OidcAudience = tmpStr + } + + if tmpStr, ok = conf.Get("common", "oidc_token_endpoint_url"); ok { + cfg.OidcTokenEndpointUrl = tmpStr + } + + return cfg +} + +type oidcServerConfig struct { + // OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer + // will be used to load public keys to verify signature and will be compared + // with the issuer claim in the OIDC token. It will be used if + // AuthenticationMethod == "oidc". By default, this value is "". + OidcIssuer string `json:"oidc_issuer"` + // OidcAudience specifies the audience OIDC tokens should contain when validated. + // If this value is empty, audience ("client ID") verification will be skipped. + // It will be used when AuthenticationMethod == "oidc". By default, this + // value is "". + OidcAudience string `json:"oidc_audience"` + // OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is + // expired. It will be used when AuthenticationMethod == "oidc". By default, this + // value is false. + OidcSkipExpiryCheck bool `json:"oidc_skip_expiry_check"` + // OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's + // issuer claim matches the issuer specified in OidcIssuer. It will be used when + // AuthenticationMethod == "oidc". By default, this value is false. + OidcSkipIssuerCheck bool `json:"oidc_skip_issuer_check"` +} + +func getDefaultOidcServerConf() oidcServerConfig { + return oidcServerConfig{ + OidcIssuer: "", + OidcAudience: "", + OidcSkipExpiryCheck: false, + OidcSkipIssuerCheck: false, + } +} + +func unmarshalOidcServerConfFromIni(conf ini.File) oidcServerConfig { + var ( + tmpStr string + ok bool + ) + + cfg := getDefaultOidcServerConf() + + if tmpStr, ok = conf.Get("common", "oidc_issuer"); ok { + cfg.OidcIssuer = tmpStr + } + + if tmpStr, ok = conf.Get("common", "oidc_audience"); ok { + cfg.OidcAudience = tmpStr + } + + if tmpStr, ok = conf.Get("common", "oidc_skip_expiry_check"); ok && tmpStr == "true" { + cfg.OidcSkipExpiryCheck = true + } else { + cfg.OidcSkipExpiryCheck = false + } + + if tmpStr, ok = conf.Get("common", "oidc_skip_issuer_check"); ok && tmpStr == "true" { + cfg.OidcSkipIssuerCheck = true + } else { + cfg.OidcSkipIssuerCheck = false + } + + return cfg +} + +type OidcAuthProvider struct { + baseConfig + + tokenGenerator *clientcredentials.Config +} + +func NewOidcAuthSetter(baseCfg baseConfig, cfg oidcClientConfig) *OidcAuthProvider { + tokenGenerator := &clientcredentials.Config{ + ClientID: cfg.OidcClientId, + ClientSecret: cfg.OidcClientSecret, + Scopes: []string{cfg.OidcAudience}, + TokenURL: cfg.OidcTokenEndpointUrl, + } + + return &OidcAuthProvider{ + baseConfig: baseCfg, + tokenGenerator: tokenGenerator, + } +} + +func (auth *OidcAuthProvider) generateAccessToken() (accessToken string, err error) { + tokenObj, err := auth.tokenGenerator.Token(context.Background()) + if err != nil { + return "", fmt.Errorf("couldn't generate OIDC token for login: %v", err) + } + return tokenObj.AccessToken, nil +} + +func (auth *OidcAuthProvider) SetLogin(loginMsg *msg.Login) (err error) { + loginMsg.PrivilegeKey, err = auth.generateAccessToken() + return err +} + +func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) { + if !auth.AuthenticateHeartBeats { + return nil + } + + pingMsg.PrivilegeKey, err = auth.generateAccessToken() + return err +} + +func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) { + if !auth.AuthenticateNewWorkConns { + return nil + } + + newWorkConnMsg.PrivilegeKey, err = auth.generateAccessToken() + return err +} + +type OidcAuthConsumer struct { + baseConfig + + verifier *oidc.IDTokenVerifier + subjectFromLogin string +} + +func NewOidcAuthVerifier(baseCfg baseConfig, cfg oidcServerConfig) *OidcAuthConsumer { + provider, err := oidc.NewProvider(context.Background(), cfg.OidcIssuer) + if err != nil { + panic(err) + } + verifierConf := oidc.Config{ + ClientID: cfg.OidcAudience, + SkipClientIDCheck: cfg.OidcAudience == "", + SkipExpiryCheck: cfg.OidcSkipExpiryCheck, + SkipIssuerCheck: cfg.OidcSkipIssuerCheck, + } + return &OidcAuthConsumer{ + baseConfig: baseCfg, + verifier: provider.Verifier(&verifierConf), + } +} + +func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) { + token, err := auth.verifier.Verify(context.Background(), loginMsg.PrivilegeKey) + if err != nil { + return fmt.Errorf("invalid OIDC token in login: %v", err) + } + auth.subjectFromLogin = token.Subject + return nil +} + +func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err error) { + token, err := auth.verifier.Verify(context.Background(), privilegeKey) + if err != nil { + return fmt.Errorf("invalid OIDC token in ping: %v", err) + } + if token.Subject != auth.subjectFromLogin { + return fmt.Errorf("received different OIDC subject in login and ping. "+ + "original subject: %s, "+ + "new subject: %s", + auth.subjectFromLogin, token.Subject) + } + return nil +} + +func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) { + if !auth.AuthenticateHeartBeats { + return nil + } + + return auth.verifyPostLoginToken(pingMsg.PrivilegeKey) +} + +func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) { + if !auth.AuthenticateNewWorkConns { + return nil + } + + return auth.verifyPostLoginToken(newWorkConnMsg.PrivilegeKey) +} diff --git a/models/auth/token.go b/models/auth/token.go new file mode 100644 index 00000000..f7be085c --- /dev/null +++ b/models/auth/token.go @@ -0,0 +1,120 @@ +// Copyright 2020 guylewin, guy@lewin.co.il +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "fmt" + "time" + + "github.com/fatedier/frp/models/msg" + "github.com/fatedier/frp/utils/util" + + "github.com/vaughan0/go-ini" +) + +type tokenConfig struct { + // Token specifies the authorization token used to create keys to be sent + // to the server. The server must have a matching token for authorization + // to succeed. By default, this value is "". + Token string `json:"token"` +} + +func getDefaultTokenConf() tokenConfig { + return tokenConfig{ + Token: "", + } +} + +func unmarshalTokenConfFromIni(conf ini.File) tokenConfig { + var ( + tmpStr string + ok bool + ) + + cfg := getDefaultTokenConf() + + if tmpStr, ok = conf.Get("common", "token"); ok { + cfg.Token = tmpStr + } + + return cfg +} + +type TokenAuthSetterVerifier struct { + baseConfig + + token string +} + +func NewTokenAuth(baseCfg baseConfig, cfg tokenConfig) *TokenAuthSetterVerifier { + return &TokenAuthSetterVerifier{ + baseConfig: baseCfg, + token: cfg.Token, + } +} + +func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) (err error) { + loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp) + return nil +} + +func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error { + if !auth.AuthenticateHeartBeats { + return nil + } + + pingMsg.Timestamp = time.Now().Unix() + pingMsg.PrivilegeKey = util.GetAuthKey(auth.token, pingMsg.Timestamp) + return nil +} + +func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error { + if !auth.AuthenticateHeartBeats { + return nil + } + + newWorkConnMsg.Timestamp = time.Now().Unix() + newWorkConnMsg.PrivilegeKey = util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp) + return nil +} + +func (auth *TokenAuthSetterVerifier) VerifyLogin(loginMsg *msg.Login) error { + if util.GetAuthKey(auth.token, loginMsg.Timestamp) != loginMsg.PrivilegeKey { + return fmt.Errorf("token in login doesn't match token from configuration") + } + return nil +} + +func (auth *TokenAuthSetterVerifier) VerifyPing(pingMsg *msg.Ping) error { + if !auth.AuthenticateHeartBeats { + return nil + } + + if util.GetAuthKey(auth.token, pingMsg.Timestamp) != pingMsg.PrivilegeKey { + return fmt.Errorf("token in heartbeat doesn't match token from configuration") + } + return nil +} + +func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error { + if !auth.AuthenticateNewWorkConns { + return nil + } + + if util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp) != newWorkConnMsg.PrivilegeKey { + return fmt.Errorf("token in NewWorkConn doesn't match token from configuration") + } + return nil +} diff --git a/models/config/client_common.go b/models/config/client_common.go index 2b5006b4..3f8c485d 100644 --- a/models/config/client_common.go +++ b/models/config/client_common.go @@ -21,12 +21,15 @@ import ( "strings" ini "github.com/vaughan0/go-ini" + + "github.com/fatedier/frp/models/auth" ) // ClientCommonConf contains information for a client service. It is // recommended to use GetDefaultClientConf instead of creating this object // directly, so that all unspecified fields have reasonable default values. type ClientCommonConf struct { + auth.AuthClientConfig // ServerAddr specifies the address of the server to connect to. By // default, this value is "0.0.0.0". ServerAddr string `json:"server_addr"` @@ -56,10 +59,6 @@ type ClientCommonConf struct { // DisableLogColor disables log colors when LogWay == "console" when set to // true. By default, this value is false. DisableLogColor bool `json:"disable_log_color"` - // Token specifies the authorization token used to create keys to be sent - // to the server. The server must have a matching token for authorization - // to succeed. By default, this value is "". - Token string `json:"token"` // AdminAddr specifies the address that the admin server binds to. By // default, this value is "127.0.0.1". AdminAddr string `json:"admin_addr"` @@ -130,7 +129,6 @@ func GetDefaultClientConf() ClientCommonConf { LogLevel: "info", LogMaxDays: 3, DisableLogColor: false, - Token: "", AdminAddr: "127.0.0.1", AdminPort: 0, AdminUser: "", @@ -158,6 +156,8 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err) } + cfg.AuthClientConfig = auth.UnmarshalAuthClientConfFromIni(conf) + var ( tmpStr string ok bool @@ -203,10 +203,6 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error } } - if tmpStr, ok = conf.Get("common", "token"); ok { - cfg.Token = tmpStr - } - if tmpStr, ok = conf.Get("common", "admin_addr"); ok { cfg.AdminAddr = tmpStr } diff --git a/models/config/server_common.go b/models/config/server_common.go index 20e92f9c..f4f3c413 100644 --- a/models/config/server_common.go +++ b/models/config/server_common.go @@ -21,6 +21,7 @@ import ( ini "github.com/vaughan0/go-ini" + "github.com/fatedier/frp/models/auth" plugin "github.com/fatedier/frp/models/plugin/server" "github.com/fatedier/frp/utils/util" ) @@ -29,6 +30,7 @@ import ( // recommended to use GetDefaultServerConf instead of creating this object // directly, so that all unspecified fields have reasonable default values. type ServerCommonConf struct { + auth.AuthServerConfig // BindAddr specifies the address that the server binds to. By default, // this value is "0.0.0.0". BindAddr string `json:"bind_addr"` @@ -101,10 +103,7 @@ type ServerCommonConf struct { // DetailedErrorsToClient defines whether to send the specific error (with // debug info) to frpc. By default, this value is true. DetailedErrorsToClient bool `json:"detailed_errors_to_client"` - // Token specifies the authorization token used to authenticate keys - // received from clients. Clients must have a matching token to be - // authorized to use the server. By default, this value is "". - Token string `json:"token"` + // SubDomainHost specifies the domain that will be attached to sub-domains // requested by the client when using Vhost proxying. For example, if this // value is set to "frps.com" and the client requested the subdomain @@ -168,7 +167,6 @@ func GetDefaultServerConf() ServerCommonConf { LogMaxDays: 3, DisableLogColor: false, DetailedErrorsToClient: true, - Token: "", SubDomainHost: "", TcpMux: true, AllowPorts: make(map[int]struct{}), @@ -195,6 +193,8 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error UnmarshalPluginsFromIni(conf, &cfg) + cfg.AuthServerConfig = auth.UnmarshalAuthServerConfFromIni(conf) + var ( tmpStr string ok bool @@ -328,8 +328,6 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error cfg.DetailedErrorsToClient = true } - cfg.Token, _ = conf.Get("common", "token") - if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok { // e.g. 1000-2000,2001,2002,3000-4000 ports, errRet := util.ParseRangeNumbers(allowPortsStr) diff --git a/models/consts/consts.go b/models/consts/consts.go index 9bf5880b..f3c480fe 100644 --- a/models/consts/consts.go +++ b/models/consts/consts.go @@ -29,4 +29,8 @@ var ( HttpsProxy string = "https" StcpProxy string = "stcp" XtcpProxy string = "xtcp" + + // authentication method + TokenAuthMethod string = "token" + OidcAuthMethod string = "oidc" ) diff --git a/models/msg/msg.go b/models/msg/msg.go index ce41c9ec..0acce5b1 100644 --- a/models/msg/msg.go +++ b/models/msg/msg.go @@ -120,7 +120,9 @@ type CloseProxy struct { } type NewWorkConn struct { - RunId string `json:"run_id"` + RunId string `json:"run_id"` + PrivilegeKey string `json:"privilege_key"` + Timestamp int64 `json:"timestamp"` } type ReqWorkConn struct { @@ -132,6 +134,7 @@ type StartWorkConn struct { DstAddr string `json:"dst_addr"` SrcPort uint16 `json:"src_port"` DstPort uint16 `json:"dst_port"` + Error string `json:"error"` } type NewVisitorConn struct { @@ -148,9 +151,12 @@ type NewVisitorConnResp struct { } type Ping struct { + PrivilegeKey string `json:"privilege_key"` + Timestamp int64 `json:"timestamp"` } type Pong struct { + Error string `json:"error"` } type UdpPacket struct { diff --git a/server/control.go b/server/control.go index 4b9227ab..e7d6f3bb 100644 --- a/server/control.go +++ b/server/control.go @@ -23,6 +23,7 @@ import ( "sync" "time" + "github.com/fatedier/frp/models/auth" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/consts" frpErr "github.com/fatedier/frp/models/errors" @@ -94,6 +95,9 @@ type Control struct { // stats collector to store stats info of clients and proxies statsCollector stats.Collector + // verifies authentication based on selected method + authVerifier auth.Verifier + // login message loginMsg *msg.Login @@ -149,6 +153,7 @@ func NewControl( pxyManager *proxy.ProxyManager, pluginManager *plugin.Manager, statsCollector stats.Collector, + authVerifier auth.Verifier, ctlConn net.Conn, loginMsg *msg.Login, serverCfg config.ServerCommonConf, @@ -163,6 +168,7 @@ func NewControl( pxyManager: pxyManager, pluginManager: pluginManager, statsCollector: statsCollector, + authVerifier: authVerifier, conn: ctlConn, loginMsg: loginMsg, sendCh: make(chan msg.Message, 10), @@ -204,7 +210,7 @@ func (ctl *Control) Start() { go ctl.stoper() } -func (ctl *Control) RegisterWorkConn(conn net.Conn) { +func (ctl *Control) RegisterWorkConn(conn net.Conn) error { xl := ctl.xl defer func() { if err := recover(); err != nil { @@ -216,9 +222,10 @@ func (ctl *Control) RegisterWorkConn(conn net.Conn) { select { case ctl.workConnCh <- conn: xl.Debug("new work connection registered") + return nil default: xl.Debug("work connection pool is full, discarding") - conn.Close() + return fmt.Errorf("work connection pool is full, discarding") } } @@ -454,6 +461,13 @@ func (ctl *Control) manager() { ctl.CloseProxy(m) xl.Info("close proxy [%s] success", m.ProxyName) case *msg.Ping: + if err := ctl.authVerifier.VerifyPing(m); err != nil { + xl.Warn("received invalid ping: %v", err) + ctl.sendCh <- &msg.Pong{ + Error: "invalid authentication in ping", + } + return + } ctl.lastPing = time.Now() xl.Debug("receive heartbeat") ctl.sendCh <- &msg.Pong{} diff --git a/server/proxy/proxy.go b/server/proxy/proxy.go index 84a04e61..d046c9cb 100644 --- a/server/proxy/proxy.go +++ b/server/proxy/proxy.go @@ -116,6 +116,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, SrcPort: uint16(srcPort), DstAddr: dstAddr, DstPort: uint16(dstPort), + Error: "", }) if err != nil { xl.Warn("failed to send message to work connection from pool: %v, times: %d", err, i) diff --git a/server/service.go b/server/service.go index 1ad7e281..d867289a 100644 --- a/server/service.go +++ b/server/service.go @@ -30,6 +30,7 @@ import ( "time" "github.com/fatedier/frp/assets" + "github.com/fatedier/frp/models/auth" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/nathole" @@ -86,6 +87,9 @@ type Service struct { // All resource managers and controllers rc *controller.ResourceController + // Verifies authentication based on selected method + authVerifier auth.Verifier + // stats collector to store server and proxies stats info statsCollector stats.Collector @@ -105,6 +109,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) { UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts), }, httpVhostRouter: vhost.NewVhostRouters(), + authVerifier: auth.NewAuthVerifier(cfg.AuthServerConfig), tlsConfig: generateTLSConfig(), cfg: cfg, } @@ -327,7 +332,9 @@ func (svr *Service) HandleListener(l net.Listener) { conn.Close() } case *msg.NewWorkConn: - svr.RegisterWorkConn(conn, m) + if err := svr.RegisterWorkConn(conn, m); err != nil { + conn.Close() + } case *msg.NewVisitorConn: if err = svr.RegisterVisitorConn(conn, m); err != nil { xl.Warn("register visitor conn error: %v", err) @@ -399,12 +406,11 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err } // Check auth. - if util.GetAuthKey(svr.cfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey { - err = fmt.Errorf("authorization failed") + if err = svr.authVerifier.VerifyLogin(loginMsg); err != nil { return } - ctl := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg) + ctl := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, svr.statsCollector, svr.authVerifier, ctlConn, loginMsg, svr.cfg) if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil { oldCtl.allShutdown.WaitDone() @@ -424,15 +430,22 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err } // RegisterWorkConn register a new work connection to control and proxies need it. -func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) { +func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) error { xl := frpNet.NewLogFromConn(workConn) ctl, exist := svr.ctlManager.GetById(newMsg.RunId) if !exist { xl.Warn("No client control found for run id [%s]", newMsg.RunId) - return + return fmt.Errorf("no client control found for run id [%s]", newMsg.RunId) } - ctl.RegisterWorkConn(workConn) - return + // Check auth. + if err := svr.authVerifier.VerifyNewWorkConn(newMsg); err != nil { + xl.Warn("Invalid authentication in NewWorkConn message on run id [%s]", newMsg.RunId) + msg.WriteMsg(workConn, &msg.StartWorkConn{ + Error: "invalid authentication in NewWorkConn", + }) + return fmt.Errorf("invalid authentication in NewWorkConn message on run id [%s]", newMsg.RunId) + } + return ctl.RegisterWorkConn(workConn) } func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVisitorConn) error { diff --git a/tests/ci/auth_test.go b/tests/ci/auth_test.go new file mode 100644 index 00000000..31074839 --- /dev/null +++ b/tests/ci/auth_test.go @@ -0,0 +1,72 @@ +package ci + +import ( + "os" + "testing" + "time" + + "github.com/fatedier/frp/tests/config" + "github.com/fatedier/frp/tests/consts" + "github.com/fatedier/frp/tests/util" + + "github.com/stretchr/testify/assert" +) + +const FRPS_TOKEN_TCP_CONF = ` +[common] +bind_addr = 0.0.0.0 +bind_port = 20000 +log_file = console +log_level = debug +authentication_method = token +token = 123456 +` + +const FRPC_TOKEN_TCP_CONF = ` +[common] +server_addr = 127.0.0.1 +server_port = 20000 +log_file = console +log_level = debug +authentication_method = token +token = 123456 +protocol = tcp + +[tcp] +type = tcp +local_port = 10701 +remote_port = 20801 +` + +func TestTcpWithTokenAuthentication(t *testing.T) { + assert := assert.New(t) + frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TOKEN_TCP_CONF) + if assert.NoError(err) { + defer os.Remove(frpsCfgPath) + } + + frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TOKEN_TCP_CONF) + if assert.NoError(err) { + defer os.Remove(frpcCfgPath) + } + + frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath}) + err = frpsProcess.Start() + if assert.NoError(err) { + defer frpsProcess.Stop() + } + + time.Sleep(200 * time.Millisecond) + + frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) + err = frpcProcess.Start() + if assert.NoError(err) { + defer frpcProcess.Stop() + } + time.Sleep(500 * time.Millisecond) + + // test tcp + res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) + assert.NoError(err) + assert.Equal(consts.TEST_TCP_ECHO_STR, res) +} diff --git a/vendor/github.com/coreos/go-oidc/.gitignore b/vendor/github.com/coreos/go-oidc/.gitignore new file mode 100644 index 00000000..c96f2f47 --- /dev/null +++ b/vendor/github.com/coreos/go-oidc/.gitignore @@ -0,0 +1,2 @@ +/bin +/gopath diff --git a/vendor/github.com/coreos/go-oidc/.travis.yml b/vendor/github.com/coreos/go-oidc/.travis.yml new file mode 100644 index 00000000..3fddaaac --- /dev/null +++ b/vendor/github.com/coreos/go-oidc/.travis.yml @@ -0,0 +1,16 @@ +language: go + +go: + - "1.12" + - "1.13" + +install: + - go get -v -t github.com/coreos/go-oidc/... + - go get golang.org/x/tools/cmd/cover + - go get golang.org/x/lint/golint + +script: + - ./test + +notifications: + email: false diff --git a/vendor/github.com/coreos/go-oidc/CONTRIBUTING.md b/vendor/github.com/coreos/go-oidc/CONTRIBUTING.md new file mode 100644 index 00000000..6662073a --- /dev/null +++ b/vendor/github.com/coreos/go-oidc/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# How to Contribute + +CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via +GitHub pull requests. This document outlines some of the conventions on +development workflow, commit message formatting, contact points and other +resources to make it easier to get your contribution accepted. + +# Certificate of Origin + +By contributing to this project you agree to the Developer Certificate of +Origin (DCO). This document was created by the Linux Kernel community and is a +simple statement that you, as a contributor, have the legal right to make the +contribution. See the [DCO](DCO) file for details. + +# Email and Chat + +The project currently uses the general CoreOS email list and IRC channel: +- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev) +- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org + +Please avoid emailing maintainers found in the MAINTAINERS file directly. They +are very busy and read the mailing lists. + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.md) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +