outposts/proxyv2 (#1365)
* outposts/proxyv2: initial commit Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> add rs256 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> more stuff Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> add forward auth an sign_out Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> match cookie name Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> re-add support for rs256 for backwards compat Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> add error handler Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> ensure unique user-agent is used Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> set cookie duration based on id_token expiry Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> build proxy v2 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> add ssl Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> add basic auth and custom header support Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> add application cert loading Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> implement whitelist Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> add redis Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> migrate embedded outpost to v2 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> remove old proxy Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> providers/proxy: make token expiration configurable Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> add metrics Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> fix tests Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: only allow one redirect URI Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix docker build for proxy Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * remove default port offset Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * add AUTHENTIK_HOST_BROWSER Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * tests: fix e2e/integration tests not using proper tags Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * remove references of old port Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix user_attributes not being loaded correctly Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * cleanup dependencies Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * cleanup Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		
							
								
								
									
										7
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -147,10 +147,15 @@ jobs: | ||||
|         run: scripts/ci_prepare.sh | ||||
|       - name: run migrations to stable | ||||
|         run: pipenv run python -m lifecycle.migrate | ||||
|       - name: prepare variables | ||||
|         id: ev | ||||
|         run: | | ||||
|           python ./scripts/gh_do_set_branch.py | ||||
|       - name: checkout current code | ||||
|         run: | | ||||
|           set -x | ||||
|           git checkout $GITHUB_REF | ||||
|           git fetch | ||||
|           git checkout ${{ steps.ev.outputs.branchName }} | ||||
|           pipenv sync --dev | ||||
|       - name: migrate to latest | ||||
|         run: pipenv run python -m lifecycle.migrate | ||||
|  | ||||
							
								
								
									
										3
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							| @ -44,8 +44,6 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v1.2.0 | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v1 | ||||
|       - name: prepare variables | ||||
| @ -70,6 +68,5 @@ jobs: | ||||
|             beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchName }}-${{ steps.ev.outputs.timestamp }} | ||||
|             beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.sha }} | ||||
|           file: ${{ matrix.type }}.Dockerfile | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|           build-args: | | ||||
|             GIT_BUILD_HASH=${{ steps.ev.outputs.sha }} | ||||
|  | ||||
| @ -0,0 +1,24 @@ | ||||
| # Generated by Django 3.2.6 on 2021-09-08 15:12 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
| import authentik.lib.utils.time | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_providers_oauth2", "0016_alter_authorizationcode_nonce"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="oauth2provider", | ||||
|             name="token_validity", | ||||
|             field=models.TextField( | ||||
|                 default="days=30", | ||||
|                 help_text="Tokens not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).", | ||||
|                 validators=[authentik.lib.utils.time.timedelta_string_validator], | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -182,7 +182,7 @@ class OAuth2Provider(Provider): | ||||
|         ), | ||||
|     ) | ||||
|     token_validity = models.TextField( | ||||
|         default="minutes=10", | ||||
|         default="days=30", | ||||
|         validators=[timedelta_string_validator], | ||||
|         help_text=_( | ||||
|             ( | ||||
|  | ||||
| @ -247,7 +247,7 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 "to": ( | ||||
|                     f"http://localhost#access_token={token.access_token}" | ||||
|                     f"&id_token={provider.encode(token.id_token.to_dict())}&token_type=bearer" | ||||
|                     f"&expires_in=600&state={state}" | ||||
|                     f"&expires_in=2592000&state={state}" | ||||
|                 ), | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
| @ -141,7 +141,7 @@ class TestToken(OAuthTestCase): | ||||
|                 "access_token": new_token.access_token, | ||||
|                 "refresh_token": new_token.refresh_token, | ||||
|                 "token_type": "bearer", | ||||
|                 "expires_in": 600, | ||||
|                 "expires_in": 2592000, | ||||
|                 "id_token": provider.encode( | ||||
|                     new_token.id_token.to_dict(), | ||||
|                 ), | ||||
| @ -190,7 +190,7 @@ class TestToken(OAuthTestCase): | ||||
|                 "access_token": new_token.access_token, | ||||
|                 "refresh_token": new_token.refresh_token, | ||||
|                 "token_type": "bearer", | ||||
|                 "expires_in": 600, | ||||
|                 "expires_in": 2592000, | ||||
|                 "id_token": provider.encode( | ||||
|                     new_token.id_token.to_dict(), | ||||
|                 ), | ||||
| @ -236,7 +236,7 @@ class TestToken(OAuthTestCase): | ||||
|                 "access_token": new_token.access_token, | ||||
|                 "refresh_token": new_token.refresh_token, | ||||
|                 "token_type": "bearer", | ||||
|                 "expires_in": 600, | ||||
|                 "expires_in": 2592000, | ||||
|                 "id_token": provider.encode( | ||||
|                     new_token.id_token.to_dict(), | ||||
|                 ), | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| """ProxyProvider API Views""" | ||||
| from typing import Any | ||||
|  | ||||
| from drf_spectacular.utils import extend_schema_field, extend_schema_serializer | ||||
| from drf_spectacular.utils import extend_schema_field | ||||
| from rest_framework.exceptions import ValidationError | ||||
| from rest_framework.fields import CharField, ListField, SerializerMethodField | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| @ -72,6 +72,7 @@ class ProxyProviderSerializer(ProviderSerializer): | ||||
|             "mode", | ||||
|             "redirect_uris", | ||||
|             "cookie_domain", | ||||
|             "token_validity", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| @ -101,7 +102,6 @@ class ProxyProviderViewSet(UsedByMixin, ModelViewSet): | ||||
|     ordering = ["name"] | ||||
|  | ||||
|  | ||||
| @extend_schema_serializer(deprecate_fields=["forward_auth_mode"]) | ||||
| class ProxyOutpostConfigSerializer(ModelSerializer): | ||||
|     """Proxy provider serializer for outposts""" | ||||
|  | ||||
|  | ||||
| @ -13,8 +13,8 @@ class ProxyDockerController(DockerController): | ||||
|     def __init__(self, outpost: Outpost, connection: DockerServiceConnection): | ||||
|         super().__init__(outpost, connection) | ||||
|         self.deployment_ports = [ | ||||
|             DeploymentPort(4180, "http", "tcp"), | ||||
|             DeploymentPort(4443, "https", "tcp"), | ||||
|             DeploymentPort(9000, "http", "tcp"), | ||||
|             DeploymentPort(9443, "https", "tcp"), | ||||
|         ] | ||||
|  | ||||
|     def _get_labels(self) -> dict[str, str]: | ||||
| @ -30,5 +30,5 @@ class ProxyDockerController(DockerController): | ||||
|         labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true" | ||||
|         labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service" | ||||
|         labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"] = "/" | ||||
|         labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port"] = "4180" | ||||
|         labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port"] = "9000" | ||||
|         return labels | ||||
|  | ||||
| @ -96,7 +96,6 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]) | ||||
|  | ||||
|     def get_reference_object(self) -> TraefikMiddleware: | ||||
|         """Get deployment object for outpost""" | ||||
|         port = 9000 if self.is_embedded else 4180 | ||||
|         return TraefikMiddleware( | ||||
|             apiVersion=f"{CRD_GROUP}/{CRD_VERSION}", | ||||
|             kind="Middleware", | ||||
| @ -107,7 +106,7 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]) | ||||
|             ), | ||||
|             spec=TraefikMiddlewareSpec( | ||||
|                 forwardAuth=TraefikMiddlewareSpecForwardAuth( | ||||
|                     address=f"http://{self.name}.{self.namespace}:{port}/akprox/auth?traefik", | ||||
|                     address=f"http://{self.name}.{self.namespace}:9000/akprox/auth?traefik", | ||||
|                     authResponseHeaders=[ | ||||
|                         "Set-Cookie", | ||||
|                         "X-Auth-Username", | ||||
|  | ||||
| @ -12,8 +12,8 @@ class ProxyKubernetesController(KubernetesController): | ||||
|     def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection): | ||||
|         super().__init__(outpost, connection) | ||||
|         self.deployment_ports = [ | ||||
|             DeploymentPort(4180, "http", "tcp"), | ||||
|             DeploymentPort(4443, "https", "tcp"), | ||||
|             DeploymentPort(9000, "http", "tcp"), | ||||
|             DeploymentPort(9443, "https", "tcp"), | ||||
|         ] | ||||
|         self.reconcilers["ingress"] = IngressReconciler | ||||
|         self.reconcilers["traefik middleware"] = TraefikMiddlewareReconciler | ||||
|  | ||||
| @ -128,8 +128,8 @@ class ProxyProvider(OutpostModel, OAuth2Provider): | ||||
|     def set_oauth_defaults(self): | ||||
|         """Ensure all OAuth2-related settings are correct""" | ||||
|         self.client_type = ClientTypes.CONFIDENTIAL | ||||
|         self.jwt_alg = JWTAlgorithms.RS256 | ||||
|         self.rsa_key = CertificateKeyPair.objects.exclude(key_data__iexact="").first() | ||||
|         self.jwt_alg = JWTAlgorithms.HS256 | ||||
|         self.rsa_key = None | ||||
|         scopes = ScopeMapping.objects.filter( | ||||
|             scope_name__in=[ | ||||
|                 SCOPE_OPENID, | ||||
| @ -139,12 +139,7 @@ class ProxyProvider(OutpostModel, OAuth2Provider): | ||||
|             ] | ||||
|         ) | ||||
|         self.property_mappings.set(scopes) | ||||
|         self.redirect_uris = "\n".join( | ||||
|             [ | ||||
|                 _get_callback_url(self.external_host), | ||||
|                 _get_callback_url(self.internal_host), | ||||
|             ] | ||||
|         ) | ||||
|         self.redirect_uris = _get_callback_url(self.external_host) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return f"Proxy Provider {self.name}" | ||||
|  | ||||
| @ -64,9 +64,8 @@ class ASGILogger: | ||||
|             return | ||||
|         return await self.app(scope, receive, send_hooked) | ||||
|  | ||||
|     def _get_ip(self, scope: Scope) -> str: | ||||
|     def _get_ip(self, headers: dict[bytes, bytes], scope: Scope) -> str: | ||||
|         client_ip = None | ||||
|         headers = dict(scope.get("headers", [])) | ||||
|         for header in ASGI_IP_HEADERS: | ||||
|             if header in headers: | ||||
|                 client_ip = headers[header].decode() | ||||
| @ -77,7 +76,8 @@ class ASGILogger: | ||||
|  | ||||
|     def log(self, scope: Scope, content_length: int, runtime: float, status_code: int, **kwargs): | ||||
|         """Outpot access logs in a structured format""" | ||||
|         host = self._get_ip(scope) | ||||
|         headers = dict(scope.get("headers", [])) | ||||
|         host = self._get_ip(headers, scope) | ||||
|         query_string = "" | ||||
|         if scope.get("query_string", b"") != b"": | ||||
|             query_string = f"?{scope.get('query_string').decode()}" | ||||
| @ -89,5 +89,6 @@ class ASGILogger: | ||||
|             status=status_code, | ||||
|             size=content_length / 1000 if content_length > 0 else 0, | ||||
|             runtime=runtime, | ||||
|             user_agent=headers.get(b"user-agent", b"").decode(), | ||||
|             **kwargs, | ||||
|         ) | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| from django.conf import settings | ||||
|  | ||||
| from authentik.lib.config import CONFIG | ||||
| from tests.e2e.utils import get_docker_tag | ||||
|  | ||||
|  | ||||
| class PytestTestRunner:  # pragma: no cover | ||||
| @ -17,7 +18,7 @@ class PytestTestRunner:  # pragma: no cover | ||||
|         CONFIG.y_set("authentik.geoip", "tests/GeoLite2-City-Test.mmdb") | ||||
|         CONFIG.y_set( | ||||
|             "outposts.docker_image_base", | ||||
|             "beryju.org/authentik/outpost-%(type)s:gh-master", | ||||
|             f"beryju.org/authentik/outpost-%(type)s:{get_docker_tag()}", | ||||
|         ) | ||||
|  | ||||
|     def run_tests(self, test_labels): | ||||
|  | ||||
| @ -9,7 +9,7 @@ import ( | ||||
|  | ||||
| 	"goauthentik.io/internal/common" | ||||
| 	"goauthentik.io/internal/outpost/ak" | ||||
| 	"goauthentik.io/internal/outpost/proxy" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2" | ||||
| ) | ||||
|  | ||||
| const helpMessage = `authentik proxy | ||||
| @ -17,7 +17,10 @@ const helpMessage = `authentik proxy | ||||
| Required environment variables: | ||||
| - AUTHENTIK_HOST: URL to connect to (format "http://authentik.company") | ||||
| - AUTHENTIK_TOKEN: Token to authenticate with | ||||
| - AUTHENTIK_INSECURE: Skip SSL Certificate verification` | ||||
| - AUTHENTIK_INSECURE: Skip SSL Certificate verification | ||||
|  | ||||
| Optionally, you can set these: | ||||
| - AUTHENTIK_HOST_BROWSER: URL to use in the browser, when it differs from AUTHENTIK_HOST` | ||||
|  | ||||
| func main() { | ||||
| 	log.SetLevel(log.DebugLevel) | ||||
| @ -46,7 +49,7 @@ func main() { | ||||
|  | ||||
| 	ac := ak.NewAPIController(*akURLActual, akToken) | ||||
|  | ||||
| 	ac.Server = proxy.NewServer(ac) | ||||
| 	ac.Server = proxyv2.NewProxyServer(ac) | ||||
|  | ||||
| 	err = ac.Start() | ||||
| 	if err != nil { | ||||
|  | ||||
| @ -12,7 +12,7 @@ import ( | ||||
| 	"goauthentik.io/internal/constants" | ||||
| 	"goauthentik.io/internal/gounicorn" | ||||
| 	"goauthentik.io/internal/outpost/ak" | ||||
| 	"goauthentik.io/internal/outpost/proxy" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2" | ||||
| 	"goauthentik.io/internal/web" | ||||
| ) | ||||
|  | ||||
| @ -99,7 +99,7 @@ func attemptProxyStart(ws *web.WebServer, u *url.URL) { | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		srv := proxy.NewServer(ac) | ||||
| 		srv := proxyv2.NewProxyServer(ac) | ||||
| 		ws.ProxyServer = srv | ||||
| 		ac.Server = srv | ||||
| 		log.WithField("logger", "authentik").Debug("attempting to start outpost") | ||||
|  | ||||
							
								
								
									
										22
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								go.mod
									
									
									
									
									
								
							| @ -6,6 +6,7 @@ require ( | ||||
| 	github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb | ||||
| 	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect | ||||
| 	github.com/coreos/go-oidc v2.2.1+incompatible | ||||
| 	github.com/garyburd/redigo v1.6.2 // indirect | ||||
| 	github.com/getsentry/sentry-go v0.11.0 | ||||
| 	github.com/go-ldap/ldap/v3 v3.4.1 | ||||
| 	github.com/go-openapi/analysis v0.20.1 // indirect | ||||
| @ -14,39 +15,30 @@ require ( | ||||
| 	github.com/go-openapi/strfmt v0.20.2 | ||||
| 	github.com/go-openapi/swag v0.19.15 // indirect | ||||
| 	github.com/go-openapi/validate v0.20.2 // indirect | ||||
| 	github.com/go-redis/redis/v7 v7.4.0 // indirect | ||||
| 	github.com/golang-jwt/jwt v3.2.2+incompatible | ||||
| 	github.com/golang/protobuf v1.5.2 // indirect | ||||
| 	github.com/google/uuid v1.3.0 | ||||
| 	github.com/gorilla/handlers v1.5.1 | ||||
| 	github.com/gorilla/mux v1.8.0 | ||||
| 	github.com/gorilla/securecookie v1.1.1 | ||||
| 	github.com/gorilla/sessions v1.2.1 | ||||
| 	github.com/gorilla/websocket v1.4.2 | ||||
| 	github.com/imdario/mergo v0.3.12 | ||||
| 	github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a | ||||
| 	github.com/justinas/alice v1.2.0 | ||||
| 	github.com/kr/pretty v0.2.1 // indirect | ||||
| 	github.com/magiconair/properties v1.8.5 // indirect | ||||
| 	github.com/mailru/easyjson v0.7.7 // indirect | ||||
| 	github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 | ||||
| 	github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3 | ||||
| 	github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc | ||||
| 	github.com/pelletier/go-toml v1.9.1 // indirect | ||||
| 	github.com/pires/go-proxyproto v0.6.0 | ||||
| 	github.com/pkg/errors v0.9.1 | ||||
| 	github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect | ||||
| 	github.com/prometheus/client_golang v1.11.0 | ||||
| 	github.com/recws-org/recws v1.3.1 | ||||
| 	github.com/sirupsen/logrus v1.8.1 | ||||
| 	github.com/spf13/afero v1.6.0 // indirect | ||||
| 	github.com/spf13/cast v1.3.1 // indirect | ||||
| 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | ||||
| 	github.com/spf13/pflag v1.0.5 // indirect | ||||
| 	github.com/spf13/viper v1.7.1 // indirect | ||||
| 	go.mongodb.org/mongo-driver v1.5.2 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect | ||||
| 	golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect | ||||
| 	golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 // indirect | ||||
| 	golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect | ||||
| 	golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 | ||||
| 	google.golang.org/appengine v1.6.7 // indirect | ||||
| 	gopkg.in/ini.v1 v1.62.0 // indirect | ||||
| 	gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b | ||||
| 	gopkg.in/square/go-jose.v2 v2.5.1 // indirect | ||||
| 	gopkg.in/yaml.v2 v2.4.0 | ||||
| ) | ||||
|  | ||||
							
								
								
									
										240
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										240
									
								
								go.sum
									
									
									
									
									
								
							| @ -12,7 +12,6 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP | ||||
| cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= | ||||
| cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= | ||||
| cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= | ||||
| cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= | ||||
| cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= | ||||
| cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= | ||||
| cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= | ||||
| @ -22,7 +21,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g | ||||
| cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= | ||||
| cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= | ||||
| cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= | ||||
| cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= | ||||
| cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= | ||||
| cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= | ||||
| cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= | ||||
| @ -36,18 +34,13 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 | ||||
| github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= | ||||
| github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= | ||||
| github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= | ||||
| github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb h1:ZVN4Iat3runWOFLaBCDVU5a9X/XikSRBosye++6gojw= | ||||
| github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss= | ||||
| github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= | ||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||
| github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= | ||||
| github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= | ||||
| github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= | ||||
| github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= | ||||
| github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= | ||||
| github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb h1:w9IDEB7P1VzNcBpOG7kMpFkZp2DkyJIUt0gDx5MBhRU= | ||||
| github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb/go.mod h1:9XMFaCeRyW7fC9XJOWQ+NdAv8VLG7ys7l3x4ozEGLUQ= | ||||
| github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= | ||||
| github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= | ||||
| github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= | ||||
| github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= | ||||
| @ -57,18 +50,12 @@ github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqR | ||||
| github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= | ||||
| github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= | ||||
| github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | ||||
| github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | ||||
| github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | ||||
| github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= | ||||
| github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= | ||||
| github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= | ||||
| github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE= | ||||
| github.com/alicebob/miniredis/v2 v2.13.0 h1:QPosMaxm+r6Qs+YcCtL2Z2a2RSdC9VfXJLpd80l8ICU= | ||||
| github.com/alicebob/miniredis/v2 v2.13.0/go.mod h1:0UIBNuf97uxrWhdVBpJvPtafKyGpL2NS2pYe0tYM97k= | ||||
| github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | ||||
| github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= | ||||
| github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= | ||||
| github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= | ||||
| github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= | ||||
| github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= | ||||
| github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= | ||||
| github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= | ||||
| github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= | ||||
| github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= | ||||
| @ -80,40 +67,30 @@ github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/ | ||||
| github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= | ||||
| github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= | ||||
| github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= | ||||
| github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= | ||||
| github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= | ||||
| github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= | ||||
| github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= | ||||
| github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= | ||||
| github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= | ||||
| github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | ||||
| github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | ||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||
| github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= | ||||
| github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= | ||||
| github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= | ||||
| github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||||
| github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | ||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||
| github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= | ||||
| github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= | ||||
| github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= | ||||
| github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= | ||||
| github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= | ||||
| github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= | ||||
| 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/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | ||||
| github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | ||||
| github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= | ||||
| github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= | ||||
| github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= | ||||
| github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| 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/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= | ||||
| github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | ||||
| github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||||
| github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= | ||||
| github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= | ||||
| github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | ||||
| github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | ||||
| github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | ||||
| @ -124,19 +101,15 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m | ||||
| github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||||
| github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= | ||||
| github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= | ||||
| github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||||
| github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= | ||||
| github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= | ||||
| github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||||
| github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE= | ||||
| github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= | ||||
| github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||||
| github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= | ||||
| github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= | ||||
| github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM= | ||||
| github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= | ||||
| github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= | ||||
| github.com/getsentry/sentry-go v0.11.0 h1:qro8uttJGvNAMr5CLcFI9CHR0aDzXl0Vs3Pmw/oTPg8= | ||||
| github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo= | ||||
| github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||||
| github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= | ||||
| github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= | ||||
| github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= | ||||
| @ -150,10 +123,13 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 | ||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||
| github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||||
| github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||||
| github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= | ||||
| github.com/go-ldap/ldap/v3 v3.4.1 h1:fU/0xli6HY02ocbMuozHAYsaHLcnkLjvho2r5a34BUU= | ||||
| github.com/go-ldap/ldap/v3 v3.4.1/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= | ||||
| github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||||
| github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | ||||
| github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= | ||||
| github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= | ||||
| github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= | ||||
| github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= | ||||
| @ -249,9 +225,6 @@ github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9G | ||||
| github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= | ||||
| github.com/go-openapi/validate v0.20.2 h1:AhqDegYV3J3iQkMPJSXkvzymHKMTw0BST3RK3hTT4ts= | ||||
| github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= | ||||
| github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= | ||||
| github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= | ||||
| github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= | ||||
| github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||||
| github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= | ||||
| github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | ||||
| @ -283,12 +256,11 @@ github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22 | ||||
| github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= | ||||
| github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= | ||||
| github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | ||||
| github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= | ||||
| github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= | ||||
| github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||
| github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= | ||||
| github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||||
| github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||||
| @ -310,13 +282,12 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W | ||||
| github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | ||||
| github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= | ||||
| github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||
| github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||
| github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= | ||||
| github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||
| github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||
| github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= | ||||
| github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= | ||||
| github.com/gomodule/redigo v1.8.1/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= | ||||
| github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||||
| github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||||
| github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||||
| @ -327,6 +298,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | ||||
| github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= | ||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= | ||||
| @ -346,43 +318,23 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ | ||||
| github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | ||||
| github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | ||||
| github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= | ||||
| github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | ||||
| 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/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= | ||||
| github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= | ||||
| github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||||
| github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||||
| github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= | ||||
| github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= | ||||
| github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= | ||||
| github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= | ||||
| github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= | ||||
| github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||
| github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= | ||||
| github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||
| github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= | ||||
| github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | ||||
| github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= | ||||
| github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= | ||||
| github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||
| github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= | ||||
| github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= | ||||
| github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= | ||||
| github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= | ||||
| github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= | ||||
| github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= | ||||
| github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= | ||||
| github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||||
| github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||||
| github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | ||||
| github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= | ||||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||
| github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||
| github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | ||||
| github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||||
| github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= | ||||
| github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= | ||||
| github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= | ||||
| github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= | ||||
| github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||||
| github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||
| github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= | ||||
| @ -394,25 +346,22 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ | ||||
| github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= | ||||
| github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= | ||||
| github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= | ||||
| github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= | ||||
| github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= | ||||
| github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= | ||||
| github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= | ||||
| github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= | ||||
| github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= | ||||
| github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= | ||||
| github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= | ||||
| github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= | ||||
| github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= | ||||
| github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= | ||||
| github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||||
| github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||||
| github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||
| github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= | ||||
| 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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= | ||||
| github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= | ||||
| github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= | ||||
| github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= | ||||
| github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= | ||||
| github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= | ||||
| github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= | ||||
| @ -421,7 +370,6 @@ github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYb | ||||
| github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= | ||||
| github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= | ||||
| github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= | ||||
| github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= | ||||
| github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= | ||||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||||
| github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= | ||||
| @ -430,12 +378,9 @@ github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 | ||||
| github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= | ||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||
| github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= | ||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||
| github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | ||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||
| github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | ||||
| github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= | ||||
| github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | ||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| @ -444,9 +389,6 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||
| github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= | ||||
| github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= | ||||
| github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||
| github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||
| github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= | ||||
| github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= | ||||
| github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= | ||||
| github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= | ||||
| github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= | ||||
| @ -457,27 +399,16 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 | ||||
| github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= | ||||
| github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= | ||||
| github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= | ||||
| github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= | ||||
| github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= | ||||
| github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||||
| github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= | ||||
| github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||||
| github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||||
| github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= | ||||
| github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= | ||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | ||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | ||||
| github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa h1:hI1uC2A3vJFjwvBn0G0a7QBRdBUp6Y048BtLAHRTKPo= | ||||
| github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa/go.mod h1:8vxFeeg++MqgCHwehSuwTlYCF0ALyDJbYJ1JsKi7v6s= | ||||
| github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= | ||||
| github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= | ||||
| github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= | ||||
| github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= | ||||
| github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||||
| github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||||
| github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= | ||||
| github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= | ||||
| github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= | ||||
| github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||||
| github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||||
| github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||
| github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||
| @ -491,6 +422,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb | ||||
| github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= | ||||
| github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= | ||||
| github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | ||||
| github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | ||||
| github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= | ||||
| github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= | ||||
| github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= | ||||
| @ -501,33 +433,17 @@ github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaD | ||||
| github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA= | ||||
| github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3 h1:NNis9uuNpG5h97Dvxxo53Scg02qBg+3Nfabg6zjFGu8= | ||||
| github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3/go.mod h1:YtrVB1/v9Td9SyjXpjYVmbdKgj9B0nPTBsdGUxy0i8U= | ||||
| github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= | ||||
| github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= | ||||
| github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc h1:jf/4meI7lkRwGoiD7Ex/ns0BekEPKZ8nsB3u2oLhLGM= | ||||
| github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc/go.mod h1:AtLBgw8gBVaipTvaFq25VIdy+TQQMsj5m7dEUhxplpo= | ||||
| github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= | ||||
| github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= | ||||
| github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||
| github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||
| github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||
| github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= | ||||
| github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= | ||||
| github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= | ||||
| github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | ||||
| github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= | ||||
| github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= | ||||
| github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= | ||||
| github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= | ||||
| github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= | ||||
| github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= | ||||
| github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= | ||||
| github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | ||||
| github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= | ||||
| github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= | ||||
| github.com/pelletier/go-toml v1.9.1 h1:a6qW1EVNZWH9WGI6CsYdD8WAylkoXBS5yv0XHlh17Tc= | ||||
| github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= | ||||
| github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= | ||||
| github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= | ||||
| github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= | ||||
| github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= | ||||
| github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk= | ||||
| @ -536,81 +452,66 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= | ||||
| 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= | ||||
| github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= | ||||
| github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M= | ||||
| github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ= | ||||
| github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= | ||||
| github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= | ||||
| github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= | ||||
| github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= | ||||
| github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= | ||||
| github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= | ||||
| github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||||
| github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= | ||||
| github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | ||||
| github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= | ||||
| github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | ||||
| github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= | ||||
| github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= | ||||
| github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= | ||||
| github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||||
| github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | ||||
| github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= | ||||
| github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | ||||
| github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= | ||||
| github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= | ||||
| github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= | ||||
| github.com/recws-org/recws v1.3.1 h1:vtRhYpgNPBs3iFyu/+zxBqNzLYgID7UPC5siThkvbs0= | ||||
| github.com/recws-org/recws v1.3.1/go.mod h1:gRH/uJLMsO7lbcecAB1Im1Zc6eKxs93ftGR0R39QeYA= | ||||
| github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= | ||||
| github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||||
| github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||||
| github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||||
| github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= | ||||
| github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= | ||||
| github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= | ||||
| github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= | ||||
| github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= | ||||
| github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= | ||||
| github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | ||||
| github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||||
| github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||||
| github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= | ||||
| github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | ||||
| github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= | ||||
| github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= | ||||
| github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||||
| 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 v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= | ||||
| github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | ||||
| github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= | ||||
| github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= | ||||
| github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= | ||||
| github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= | ||||
| github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= | ||||
| github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | ||||
| github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= | ||||
| github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | ||||
| github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | ||||
| github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= | ||||
| github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | ||||
| github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= | ||||
| github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= | ||||
| github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||
| github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= | ||||
| github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= | ||||
| github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= | ||||
| github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= | ||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= | ||||
| github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= | ||||
| github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= | ||||
| github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= | ||||
| github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= | ||||
| github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= | ||||
| github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= | ||||
| github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= | ||||
| @ -621,10 +522,6 @@ github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBn | ||||
| github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= | ||||
| github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= | ||||
| github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= | ||||
| github.com/vmihailenco/msgpack/v4 v4.3.11 h1:Q47CePddpNGNhk4GCnAx9DDtASi2rasatE0cd26cZoE= | ||||
| github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= | ||||
| github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= | ||||
| github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= | ||||
| github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= | ||||
| github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= | ||||
| github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= | ||||
| @ -633,11 +530,8 @@ github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHM | ||||
| github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= | ||||
| github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= | ||||
| github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= | ||||
| github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= | ||||
| github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= | ||||
| github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= | ||||
| github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 h1:1+FQ4Ns+UZtUiQ4lP0sTCyKSQ0EXoiwAdHZB0Pd5t9Q= | ||||
| github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997/go.mod h1:DIGbh/f5XMAessMV/uaIik81gkDVjUeQ9ApdaU7wRKE= | ||||
| github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= | ||||
| github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= | ||||
| github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= | ||||
| @ -645,11 +539,6 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ | ||||
| github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= | ||||
| github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= | ||||
| github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0= | ||||
| github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= | ||||
| go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= | ||||
| go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= | ||||
| go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= | ||||
| go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= | ||||
| @ -664,13 +553,8 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||
| go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | ||||
| go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= | ||||
| go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | ||||
| go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | ||||
| go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | ||||
| golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| @ -681,7 +565,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U | ||||
| golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| @ -723,9 +606,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r | ||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| @ -735,14 +616,12 @@ golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn | ||||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | ||||
| golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | ||||
| golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| @ -753,7 +632,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ | ||||
| golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| @ -782,15 +660,12 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ | ||||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| @ -808,14 +683,11 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w | ||||
| golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| @ -827,14 +699,16 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w | ||||
| golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| 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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= | ||||
| golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= | ||||
| golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| @ -848,7 +722,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| @ -875,7 +748,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw | ||||
| golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| @ -921,7 +793,6 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ | ||||
| google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= | ||||
| google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= | ||||
| google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= | ||||
| google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= | ||||
| google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= | ||||
| google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | ||||
| google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||
| @ -959,11 +830,9 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY | ||||
| google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= | ||||
| google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= | ||||
| google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||||
| google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | ||||
| google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | ||||
| google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | ||||
| google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | ||||
| google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= | ||||
| @ -973,7 +842,6 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 | ||||
| google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= | ||||
| google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= | ||||
| google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= | ||||
| google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= | ||||
| google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= | ||||
| google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | ||||
| google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | ||||
| @ -989,6 +857,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 | ||||
| google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= | ||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | ||||
| gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b h1:U/Uqd1232+wrnHOvWNaxrNqn/kFnr4yu4blgPtQt0N8= | ||||
| gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b/go.mod h1:fgfIZMlsafAHpspcks2Bul+MWUNw/2dyQmjC2faKjtg= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| @ -998,23 +868,15 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | ||||
| gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | ||||
| gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= | ||||
| gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= | ||||
| gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= | ||||
| gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= | ||||
| gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= | ||||
| gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= | ||||
| gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= | ||||
| gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= | ||||
| gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= | ||||
| gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= | ||||
| gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= | ||||
| gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | ||||
| gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= | ||||
| gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||
|  | ||||
| @ -14,7 +14,7 @@ func BUILD() string { | ||||
| } | ||||
|  | ||||
| func OutpostUserAgent() string { | ||||
| 	return fmt.Sprintf("authentik-outpost@%s (%s)", VERSION, BUILD()) | ||||
| 	return fmt.Sprintf("authentik-outpost@%s (build=%s)", VERSION, BUILD()) | ||||
| } | ||||
|  | ||||
| const VERSION = "2021.8.4" | ||||
|  | ||||
| @ -11,6 +11,7 @@ import ( | ||||
| 	"github.com/go-openapi/strfmt" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 	"github.com/recws-org/recws" | ||||
| 	"goauthentik.io/api" | ||||
| 	"goauthentik.io/internal/constants" | ||||
| @ -43,11 +44,10 @@ type APIController struct { | ||||
| // NewAPIController initialise new API Controller instance from URL and API token | ||||
| func NewAPIController(akURL url.URL, token string) *APIController { | ||||
| 	config := api.NewConfiguration() | ||||
| 	config.UserAgent = constants.OutpostUserAgent() | ||||
| 	config.Host = akURL.Host | ||||
| 	config.Scheme = akURL.Scheme | ||||
| 	config.HTTPClient = &http.Client{ | ||||
| 		Transport: NewTracingTransport(context.TODO(), GetTLSTransport()), | ||||
| 		Transport: NewUserAgentTransport(constants.OutpostUserAgent(), NewTracingTransport(context.TODO(), GetTLSTransport())), | ||||
| 	} | ||||
| 	config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token)) | ||||
|  | ||||
| @ -89,6 +89,13 @@ func NewAPIController(akURL url.URL, token string) *APIController { | ||||
| 	} | ||||
| 	ac.logger.Debugf("HA Reload offset: %s", ac.reloadOffset) | ||||
| 	ac.initWS(akURL, strfmt.UUID(outpost.Pk)) | ||||
|  | ||||
| 	OutpostInfo.With(prometheus.Labels{ | ||||
| 		"uuid":    ac.instanceUUID.String(), | ||||
| 		"name":    outpost.Name, | ||||
| 		"version": constants.VERSION, | ||||
| 		"build":   constants.BUILD(), | ||||
| 	}).Set(1) | ||||
| 	return ac | ||||
| } | ||||
|  | ||||
| @ -111,6 +118,13 @@ func (a *APIController) StartBackgorundTasks() error { | ||||
| 	err := a.Server.Refresh() | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "failed to run initial refresh") | ||||
| 	} else { | ||||
| 		LastUpdate.With(prometheus.Labels{ | ||||
| 			"uuid":    a.instanceUUID.String(), | ||||
| 			"name":    a.Outpost.Name, | ||||
| 			"version": constants.VERSION, | ||||
| 			"build":   constants.BUILD(), | ||||
| 		}).SetToCurrentTime() | ||||
| 	} | ||||
| 	go func() { | ||||
| 		a.logger.Debug("Starting WS Handler...") | ||||
|  | ||||
| @ -1,16 +0,0 @@ | ||||
| package ak | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"goauthentik.io/api" | ||||
| ) | ||||
|  | ||||
| func (a *APIController) Update() ([]api.ProxyOutpostConfig, error) { | ||||
| 	providers, _, err := a.Client.OutpostsApi.OutpostsProxyList(context.Background()).Execute() | ||||
| 	if err != nil { | ||||
| 		a.logger.WithError(err).Error("Failed to fetch providers") | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return providers.Results, nil | ||||
| } | ||||
| @ -11,6 +11,7 @@ import ( | ||||
|  | ||||
| 	"github.com/go-openapi/strfmt" | ||||
| 	"github.com/gorilla/websocket" | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 	"github.com/recws-org/recws" | ||||
| 	"goauthentik.io/internal/constants" | ||||
| ) | ||||
| @ -74,17 +75,32 @@ func (ac *APIController) startWSHandler() { | ||||
| 		var wsMsg websocketMessage | ||||
| 		err := ac.wsConn.ReadJSON(&wsMsg) | ||||
| 		if err != nil { | ||||
| 			ConnectionStatus.With(prometheus.Labels{ | ||||
| 				"uuid": ac.instanceUUID.String(), | ||||
| 				"name": ac.Outpost.Name, | ||||
| 			}).Set(0) | ||||
| 			logger.WithError(err).Warning("ws write error, reconnecting") | ||||
| 			ac.wsConn.CloseAndReconnect() | ||||
| 			time.Sleep(time.Second * 5) | ||||
| 			continue | ||||
| 		} | ||||
| 		ConnectionStatus.With(prometheus.Labels{ | ||||
| 			"uuid": ac.instanceUUID.String(), | ||||
| 			"name": ac.Outpost.Name, | ||||
| 		}).Set(1) | ||||
| 		if wsMsg.Instruction == WebsocketInstructionTriggerUpdate { | ||||
| 			time.Sleep(ac.reloadOffset) | ||||
| 			logger.Debug("Got update trigger...") | ||||
| 			err := ac.Server.Refresh() | ||||
| 			if err != nil { | ||||
| 				logger.WithError(err).Debug("Failed to update") | ||||
| 			} else { | ||||
| 				LastUpdate.With(prometheus.Labels{ | ||||
| 					"uuid":    ac.instanceUUID.String(), | ||||
| 					"name":    ac.Outpost.Name, | ||||
| 					"version": constants.VERSION, | ||||
| 					"build":   constants.BUILD(), | ||||
| 				}).SetToCurrentTime() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @ -110,6 +126,11 @@ func (ac *APIController) startWSHealth() { | ||||
| 			ac.logger.WithField("loop", "ws-health").WithError(err).Warning("ws write error, reconnecting") | ||||
| 			ac.wsConn.CloseAndReconnect() | ||||
| 			continue | ||||
| 		} else { | ||||
| 			ConnectionStatus.With(prometheus.Labels{ | ||||
| 				"uuid": ac.instanceUUID.String(), | ||||
| 				"name": ac.Outpost.Name, | ||||
| 			}).Set(1) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -121,6 +142,13 @@ func (ac *APIController) startIntervalUpdater() { | ||||
| 		err := ac.Server.Refresh() | ||||
| 		if err != nil { | ||||
| 			logger.WithError(err).Debug("Failed to update") | ||||
| 		} else { | ||||
| 			LastUpdate.With(prometheus.Labels{ | ||||
| 				"uuid":    ac.instanceUUID.String(), | ||||
| 				"name":    ac.Outpost.Name, | ||||
| 				"version": constants.VERSION, | ||||
| 				"build":   constants.BUILD(), | ||||
| 			}).SetToCurrentTime() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -5,6 +5,7 @@ import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/getsentry/sentry-go" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
| 
 | ||||
| type tracingTransport struct { | ||||
| @ -21,5 +22,10 @@ func (tt *tracingTransport) RoundTrip(r *http.Request) (*http.Response, error) { | ||||
| 	span.SetTag("url", r.URL.String()) | ||||
| 	span.SetTag("method", r.Method) | ||||
| 	defer span.Finish() | ||||
| 	return tt.inner.RoundTrip(r.WithContext(span.Context())) | ||||
| 	res, err := tt.inner.RoundTrip(r.WithContext(span.Context())) | ||||
| 	log.WithFields(log.Fields{ | ||||
| 		"url":    r.URL.String(), | ||||
| 		"method": r.Method, | ||||
| 	}).Trace("http request") | ||||
| 	return res, err | ||||
| } | ||||
							
								
								
									
										19
									
								
								internal/outpost/ak/http_user_agent.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								internal/outpost/ak/http_user_agent.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| package ak | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| type userAgentTransport struct { | ||||
| 	inner http.RoundTripper | ||||
| 	ua    string | ||||
| } | ||||
|  | ||||
| func NewUserAgentTransport(ua string, inner http.RoundTripper) *userAgentTransport { | ||||
| 	return &userAgentTransport{inner, ua} | ||||
| } | ||||
|  | ||||
| func (uat *userAgentTransport) RoundTrip(r *http.Request) (*http.Response, error) { | ||||
| 	r.Header.Set("User-Agent", uat.ua) | ||||
| 	return uat.inner.RoundTrip(r) | ||||
| } | ||||
							
								
								
									
										21
									
								
								internal/outpost/ak/metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								internal/outpost/ak/metrics.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| package ak | ||||
|  | ||||
| import ( | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 	"github.com/prometheus/client_golang/prometheus/promauto" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	OutpostInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{ | ||||
| 		Name: "authentik_outpost_info", | ||||
| 		Help: "Outpost info", | ||||
| 	}, []string{"uuid", "name", "version", "build"}) | ||||
| 	LastUpdate = promauto.NewGaugeVec(prometheus.GaugeOpts{ | ||||
| 		Name: "authentik_outpost_last_update", | ||||
| 		Help: "Time of last update", | ||||
| 	}, []string{"uuid", "name", "version", "build"}) | ||||
| 	ConnectionStatus = promauto.NewGaugeVec(prometheus.GaugeOpts{ | ||||
| 		Name: "authentik_outpost_connection", | ||||
| 		Help: "Connection status", | ||||
| 	}, []string{"uuid", "name"}) | ||||
| ) | ||||
| @ -59,10 +59,9 @@ func NewFlowExecutor(ctx context.Context, flowSlug string, refConfig *api.Config | ||||
| 	config := api.NewConfiguration() | ||||
| 	config.Host = refConfig.Host | ||||
| 	config.Scheme = refConfig.Scheme | ||||
| 	config.UserAgent = constants.OutpostUserAgent() | ||||
| 	config.HTTPClient = &http.Client{ | ||||
| 		Jar:       jar, | ||||
| 		Transport: ak.NewTracingTransport(ctx, ak.GetTLSTransport()), | ||||
| 		Transport: ak.NewUserAgentTransport(constants.OutpostUserAgent(), ak.NewTracingTransport(ctx, ak.GetTLSTransport())), | ||||
| 	} | ||||
| 	token := strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1] | ||||
| 	config.AddDefaultHeader(HeaderAuthentikOutpostToken, token) | ||||
|  | ||||
| @ -66,7 +66,7 @@ func (ls *LDAPServer) Refresh() error { | ||||
| } | ||||
|  | ||||
| func (ls *LDAPServer) StartHTTPServer() error { | ||||
| 	listen := "0.0.0.0:4180" // same port as proxy | ||||
| 	listen := "0.0.0.0:9000" // same port as proxy | ||||
| 	m := http.NewServeMux() | ||||
| 	m.HandleFunc("/akprox/ping", func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		rw.WriteHeader(204) | ||||
|  | ||||
| @ -1,49 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"net/url" | ||||
|  | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/api" | ||||
| ) | ||||
|  | ||||
| func (s *Server) Refresh() error { | ||||
| 	providers, err := s.ak.Update() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if providers == nil { | ||||
| 		s.logger.Debug("Providers have not changed, not updating") | ||||
| 		return nil | ||||
| 	} | ||||
| 	bundles := s.bundleProviders(providers) | ||||
| 	s.updateHTTPServer(bundles) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *Server) bundleProviders(providers []api.ProxyOutpostConfig) []*providerBundle { | ||||
| 	bundles := make([]*providerBundle, len(providers)) | ||||
| 	for idx, provider := range providers { | ||||
| 		externalHost, err := url.Parse(provider.ExternalHost) | ||||
| 		if err != nil { | ||||
| 			log.WithError(err).Warning("Failed to parse URL, skipping provider") | ||||
| 		} | ||||
| 		bundles[idx] = &providerBundle{ | ||||
| 			s:             s, | ||||
| 			Host:          externalHost.Host, | ||||
| 			log:           log.WithField("logger", "authentik.outpost.proxy-bundle").WithField("provider", provider.Name), | ||||
| 			endSessionUrl: provider.OidcConfiguration.EndSessionEndpoint, | ||||
| 		} | ||||
| 		bundles[idx].Build(provider) | ||||
| 	} | ||||
| 	return bundles | ||||
| } | ||||
|  | ||||
| func (s *Server) updateHTTPServer(bundles []*providerBundle) { | ||||
| 	newMap := make(map[string]*providerBundle) | ||||
| 	for _, bundle := range bundles { | ||||
| 		newMap[bundle.Host] = bundle | ||||
| 	} | ||||
| 	s.logger.Debug("Swapped maps") | ||||
| 	s.Handlers = newMap | ||||
| } | ||||
| @ -1,169 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/jinzhu/copier" | ||||
| 	"github.com/justinas/alice" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/middleware" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/validation" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/api" | ||||
| ) | ||||
|  | ||||
| type providerBundle struct { | ||||
| 	http.Handler | ||||
|  | ||||
| 	s     *Server | ||||
| 	proxy *OAuthProxy | ||||
| 	Host  string | ||||
|  | ||||
| 	endSessionUrl string | ||||
| 	Mode          *api.ProxyMode | ||||
|  | ||||
| 	cert *tls.Certificate | ||||
|  | ||||
| 	log *log.Entry | ||||
| } | ||||
|  | ||||
| func intToPointer(i int) *int { | ||||
| 	return &i | ||||
| } | ||||
|  | ||||
| func (pb *providerBundle) replaceLocal(url string) string { | ||||
| 	if strings.HasPrefix(url, "http://localhost:8000") { | ||||
| 		authentikHost, c := pb.s.ak.Outpost.Config["authentik_host"] | ||||
| 		if !c || authentikHost == "" { | ||||
| 			pb.log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.") | ||||
| 			return url | ||||
| 		} | ||||
| 		f := strings.ReplaceAll(url, "http://localhost:8000", authentikHost.(string)) | ||||
| 		return f | ||||
| 	} | ||||
| 	return url | ||||
| } | ||||
|  | ||||
| func (pb *providerBundle) prepareOpts(provider api.ProxyOutpostConfig) *options.Options { | ||||
| 	// We need to save the mode in the bundle | ||||
| 	// Since for the embedded outpost we only switch for fully proxy providers | ||||
| 	pb.Mode = provider.Mode | ||||
|  | ||||
| 	externalHost, err := url.Parse(provider.ExternalHost) | ||||
| 	if err != nil { | ||||
| 		log.WithError(err).Warning("Failed to parse URL, skipping provider") | ||||
| 		return nil | ||||
| 	} | ||||
| 	providerOpts := &options.Options{} | ||||
| 	err = copier.Copy(&providerOpts, getCommonOptions()) | ||||
| 	if err != nil { | ||||
| 		log.WithError(err).Warning("Failed to copy options, skipping provider") | ||||
| 		return nil | ||||
| 	} | ||||
| 	providerOpts.ClientID = *provider.ClientId | ||||
| 	providerOpts.ClientSecret = *provider.ClientSecret | ||||
|  | ||||
| 	providerOpts.Cookie.Secret = *provider.CookieSecret | ||||
| 	providerOpts.Cookie.Secure = externalHost.Scheme == "https" | ||||
|  | ||||
| 	providerOpts.SkipOIDCDiscovery = true | ||||
| 	providerOpts.OIDCIssuerURL = pb.replaceLocal(provider.OidcConfiguration.Issuer) | ||||
| 	providerOpts.LoginURL = pb.replaceLocal(provider.OidcConfiguration.AuthorizationEndpoint) | ||||
| 	providerOpts.RedeemURL = pb.replaceLocal(provider.OidcConfiguration.TokenEndpoint) | ||||
| 	providerOpts.OIDCJwksURL = pb.replaceLocal(provider.OidcConfiguration.JwksUri) | ||||
| 	providerOpts.ProfileURL = pb.replaceLocal(provider.OidcConfiguration.UserinfoEndpoint) | ||||
| 	providerOpts.ValidateURL = pb.replaceLocal(provider.OidcConfiguration.UserinfoEndpoint) | ||||
| 	providerOpts.AcrValues = "goauthentik.io/providers/oauth2/default" | ||||
|  | ||||
| 	if *provider.SkipPathRegex != "" { | ||||
| 		skipRegexes := strings.Split(*provider.SkipPathRegex, "\n") | ||||
| 		providerOpts.SkipAuthRegex = skipRegexes | ||||
| 	} | ||||
|  | ||||
| 	if *provider.Mode == api.PROXYMODE_FORWARD_SINGLE || *provider.Mode == api.PROXYMODE_FORWARD_DOMAIN { | ||||
| 		providerOpts.UpstreamServers = []options.Upstream{ | ||||
| 			{ | ||||
| 				ID:         "static", | ||||
| 				Static:     true, | ||||
| 				StaticCode: intToPointer(202), | ||||
| 				Path:       "/", | ||||
| 			}, | ||||
| 		} | ||||
| 	} else { | ||||
| 		providerOpts.UpstreamServers = []options.Upstream{ | ||||
| 			{ | ||||
| 				ID:                    "default", | ||||
| 				URI:                   *provider.InternalHost, | ||||
| 				Path:                  "/", | ||||
| 				InsecureSkipTLSVerify: !(*provider.InternalHostSslValidation), | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if provider.Certificate.Get() != nil { | ||||
| 		kp := provider.Certificate.Get() | ||||
| 		err := pb.s.cs.AddKeypair(*kp) | ||||
| 		if err != nil { | ||||
| 			pb.log.WithError(err).Warning("Failed to initially fetch certificate") | ||||
| 		} | ||||
| 		pb.cert = pb.s.cs.Get(*kp) | ||||
| 	} | ||||
| 	return providerOpts | ||||
| } | ||||
|  | ||||
| func (pb *providerBundle) Build(provider api.ProxyOutpostConfig) { | ||||
| 	opts := pb.prepareOpts(provider) | ||||
|  | ||||
| 	if *provider.Mode == api.PROXYMODE_FORWARD_DOMAIN { | ||||
| 		opts.Cookie.Domains = []string{*provider.CookieDomain} | ||||
| 	} | ||||
|  | ||||
| 	chain := alice.New() | ||||
|  | ||||
| 	if opts.ForceHTTPS { | ||||
| 		_, httpsPort, err := net.SplitHostPort(opts.HTTPSAddress) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("FATAL: invalid HTTPS address %q: %v", opts.HTTPAddress, err) | ||||
| 		} | ||||
| 		chain = chain.Append(middleware.NewRedirectToHTTPS(httpsPort)) | ||||
| 	} | ||||
|  | ||||
| 	healthCheckPaths := []string{opts.PingPath} | ||||
| 	healthCheckUserAgents := []string{opts.PingUserAgent} | ||||
|  | ||||
| 	// To silence logging of health checks, register the health check handler before | ||||
| 	// the logging handler | ||||
| 	if opts.Logging.SilencePing { | ||||
| 		chain = chain.Append(middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents), LoggingHandler) | ||||
| 	} else { | ||||
| 		chain = chain.Append(LoggingHandler, middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents)) | ||||
| 	} | ||||
|  | ||||
| 	err := validation.Validate(opts) | ||||
| 	if err != nil { | ||||
| 		log.Printf("%s", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	oauthproxy, err := NewOAuthProxy(opts, provider, pb.s.ak.Client.GetConfig().HTTPClient) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("ERROR: Failed to initialise OAuth2 Proxy: %v", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	if *provider.BasicAuthEnabled { | ||||
| 		oauthproxy.SetBasicAuth = true | ||||
| 		oauthproxy.BasicAuthUserAttribute = *provider.BasicAuthUserAttribute | ||||
| 		oauthproxy.BasicAuthPasswordAttribute = *provider.BasicAuthPasswordAttribute | ||||
| 	} | ||||
|  | ||||
| 	oauthproxy.endSessionEndpoint = pb.endSessionUrl | ||||
| 	oauthproxy.ExternalHost = pb.Host | ||||
|  | ||||
| 	pb.proxy = oauthproxy | ||||
| 	pb.Handler = chain.Then(oauthproxy) | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type Claims struct { | ||||
| 	Proxy struct { | ||||
| 		UserAttributes map[string]interface{} `json:"user_attributes"` | ||||
| 	} `json:"ak_proxy"` | ||||
| 	Groups []string `json:"groups"` | ||||
| } | ||||
|  | ||||
| func (c *Claims) FromIDToken(idToken string) error { | ||||
| 	// id_token is a base64 encode ID token payload | ||||
| 	// https://developers.google.com/accounts/docs/OAuth2Login#obtainuserinfo | ||||
| 	jwt := strings.Split(idToken, ".") | ||||
| 	jwtData := strings.TrimSuffix(jwt[1], "=") | ||||
| 	b, err := base64.RawURLEncoding.DecodeString(jwtData) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = json.Unmarshal(b, c) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @ -1,39 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	log "github.com/sirupsen/logrus" | ||||
|  | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||
| 	"goauthentik.io/internal/config" | ||||
| ) | ||||
|  | ||||
| func getCommonOptions() *options.Options { | ||||
| 	commonOpts := options.NewOptions() | ||||
| 	commonOpts.Cookie.Name = "authentik_proxy" | ||||
| 	commonOpts.Cookie.Expire = 24 * time.Hour | ||||
| 	commonOpts.EmailDomains = []string{"*"} | ||||
| 	commonOpts.ProviderType = "oidc" | ||||
| 	commonOpts.ProxyPrefix = "/akprox" | ||||
| 	commonOpts.Logging.SilencePing = true | ||||
| 	commonOpts.SetAuthorization = false | ||||
| 	commonOpts.Scope = "openid email profile ak_proxy" | ||||
| 	if config.G.Redis.Host != "" { | ||||
| 		protocol := "redis" | ||||
| 		if config.G.Redis.TLS { | ||||
| 			protocol = "rediss" | ||||
| 		} | ||||
| 		url := fmt.Sprintf("%s://@%s:%d/%d", protocol, config.G.Redis.Host, config.G.Redis.Port, config.G.Redis.OutpostSessionDB) | ||||
| 		log.WithField("url", url).Info("Using redis session backend") | ||||
| 		commonOpts.Session.Redis = options.RedisStoreOptions{ | ||||
| 			ConnectionURL: url, | ||||
| 			Password:      config.G.Redis.Password, | ||||
| 		} | ||||
| 		if config.G.Redis.TLSReqs != "" { | ||||
| 			commonOpts.Session.Redis.InsecureSkipTLSVerify = true | ||||
| 		} | ||||
| 	} | ||||
| 	return commonOpts | ||||
| } | ||||
| @ -1,69 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/cookies" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| ) | ||||
|  | ||||
| // MakeCSRFCookie creates a cookie for CSRF | ||||
| func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie { | ||||
| 	return p.makeCookie(req, p.CSRFCookieName, value, expiration, now) | ||||
| } | ||||
|  | ||||
| func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie { | ||||
| 	cookieDomain := cookies.GetCookieDomain(req, p.CookieDomains) | ||||
|  | ||||
| 	if cookieDomain != "" { | ||||
| 		domain := web.GetHost(req) | ||||
| 		if h, _, err := net.SplitHostPort(domain); err == nil { | ||||
| 			domain = h | ||||
| 		} | ||||
| 		if !strings.HasSuffix(domain, cookieDomain) { | ||||
| 			p.logger.Errorf("Warning: request host is %q but using configured cookie domain of %q", domain, cookieDomain) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &http.Cookie{ | ||||
| 		Name:     name, | ||||
| 		Value:    value, | ||||
| 		Path:     p.CookiePath, | ||||
| 		Domain:   cookieDomain, | ||||
| 		HttpOnly: p.CookieHTTPOnly, | ||||
| 		Secure:   p.CookieSecure, | ||||
| 		Expires:  now.Add(expiration), | ||||
| 		SameSite: cookies.ParseSameSite(p.CookieSameSite), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ClearCSRFCookie creates a cookie to unset the CSRF cookie stored in the user's | ||||
| // session | ||||
| func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) { | ||||
| 	http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now())) | ||||
| } | ||||
|  | ||||
| // SetCSRFCookie adds a CSRF cookie to the response | ||||
| func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, val string) { | ||||
| 	http.SetCookie(rw, p.MakeCSRFCookie(req, val, p.CookieExpire, time.Now())) | ||||
| } | ||||
|  | ||||
| // ClearSessionCookie creates a cookie to unset the user's authentication cookie | ||||
| // stored in the user's session | ||||
| func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) error { | ||||
| 	return p.sessionStore.Clear(rw, req) | ||||
| } | ||||
|  | ||||
| // LoadCookiedSession reads the user's authentication details from the request | ||||
| func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessionsapi.SessionState, error) { | ||||
| 	return p.sessionStore.Load(req) | ||||
| } | ||||
|  | ||||
| // SaveSession creates a new session cookie value and sets this on the response | ||||
| func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *sessionsapi.SessionState) error { | ||||
| 	return p.sessionStore.Save(rw, req, s) | ||||
| } | ||||
| @ -1,227 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" | ||||
| ) | ||||
|  | ||||
| // GetRedirectURI returns the redirectURL that the upstream OAuth Provider will | ||||
| // redirect clients to once authenticated | ||||
| func (p *OAuthProxy) GetRedirectURI(host string) string { | ||||
| 	// default to the request Host if not set | ||||
| 	if p.redirectURL.Host != "" { | ||||
| 		return p.redirectURL.String() | ||||
| 	} | ||||
| 	u := *p.redirectURL | ||||
| 	if u.Scheme == "" { | ||||
| 		if p.CookieSecure { | ||||
| 			u.Scheme = httpsScheme | ||||
| 		} else { | ||||
| 			u.Scheme = httpScheme | ||||
| 		} | ||||
| 	} | ||||
| 	u.Host = host | ||||
| 	return u.String() | ||||
| } | ||||
|  | ||||
| // HTTPClient is the context key to use with golang.org/x/net/context's | ||||
| // WithValue function to associate an *http.Client value with a context. | ||||
| var HTTPClient ContextKey | ||||
|  | ||||
| // ContextKey is just an empty struct. It exists so HTTPClient can be | ||||
| // an immutable public variable with a unique type. It's immutable | ||||
| // because nobody else can create a ContextKey, being unexported. | ||||
| type ContextKey struct{} | ||||
|  | ||||
| func (p *OAuthProxy) redeemCode(ctx context.Context, host, code string) (s *sessionsapi.SessionState, err error) { | ||||
| 	if code == "" { | ||||
| 		return nil, errors.New("missing code") | ||||
| 	} | ||||
| 	redirectURI := p.GetRedirectURI(host) | ||||
| 	redeemCtx := context.WithValue(ctx, HTTPClient, p.client) | ||||
| 	s, err = p.provider.Redeem(redeemCtx, redirectURI, code) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if s.Email == "" { | ||||
| 		s.Email, err = p.provider.GetEmailAddress(ctx, s) | ||||
| 	} | ||||
|  | ||||
| 	if s.PreferredUsername == "" { | ||||
| 		s.PreferredUsername, err = p.provider.GetPreferredUsername(ctx, s) | ||||
| 		if err != nil && err.Error() == "not implemented" { | ||||
| 			err = nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if s.User == "" { | ||||
| 		s.User, err = p.provider.GetUserName(ctx, s) | ||||
| 		if err != nil && err.Error() == "not implemented" { | ||||
| 			err = nil | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetRedirect reads the query parameter to get the URL to redirect clients to | ||||
| // once authenticated with the OAuthProxy | ||||
| func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) { | ||||
| 	err = req.ParseForm() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	redirect = req.Header.Get("X-Auth-Request-Redirect") | ||||
| 	if req.Form.Get("rd") != "" { | ||||
| 		redirect = req.Form.Get("rd") | ||||
| 	} | ||||
| 	if !p.IsValidRedirect(redirect) { | ||||
| 		// Use RequestURI to preserve ?query | ||||
| 		redirect = req.URL.RequestURI() | ||||
| 		if strings.HasPrefix(redirect, p.ProxyPrefix) { | ||||
| 			redirect = "/" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // IsValidRedirect checks whether the redirect URL is whitelisted | ||||
| func (p *OAuthProxy) IsValidRedirect(redirect string) bool { | ||||
| 	switch { | ||||
| 	case redirect == "": | ||||
| 		// The user didn't specify a redirect, should fallback to `/` | ||||
| 		return false | ||||
| 	case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !invalidRedirectRegex.MatchString(redirect): | ||||
| 		return true | ||||
| 	case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"): | ||||
| 		redirectURL, err := url.Parse(redirect) | ||||
| 		if err != nil { | ||||
| 			p.logger.WithField("redirect", redirect).Printf("Rejecting invalid redirect %q: scheme unsupported or missing", redirect) | ||||
| 			return false | ||||
| 		} | ||||
| 		redirectHostname := redirectURL.Hostname() | ||||
|  | ||||
| 		for _, domain := range p.CookieDomains { | ||||
| 			if strings.HasSuffix(redirectHostname, domain) { | ||||
| 				p.logger.WithField("redirect", redirect).WithField("domain", domain).Debug("allowing redirect") | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		p.logger.WithField("redirect", redirect).Printf("Rejecting invalid redirect %q: domain / port not in whitelist", redirect) | ||||
| 		return false | ||||
| 	default: | ||||
| 		p.logger.WithField("redirect", redirect).Printf("Rejecting invalid redirect %q: not an absolute or relative URL", redirect) | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // IsWhitelistedRequest is used to check if auth should be skipped for this request | ||||
| func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool { | ||||
| 	isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS" | ||||
| 	return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path) | ||||
| } | ||||
|  | ||||
| // IsWhitelistedPath is used to check if the request path is allowed without auth | ||||
| func (p *OAuthProxy) IsWhitelistedPath(path string) bool { | ||||
| 	for _, u := range p.compiledRegex { | ||||
| 		if u.MatchString(path) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // OAuthStart starts the OAuth2 authentication flow | ||||
| func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) { | ||||
| 	prepareNoCache(rw) | ||||
| 	nonce, err := encryption.Nonce() | ||||
| 	if err != nil { | ||||
| 		p.logger.Errorf("Error obtaining nonce: %v", err) | ||||
| 		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	p.SetCSRFCookie(rw, req, nonce) | ||||
| 	redirect, err := p.GetRedirect(req) | ||||
| 	if err != nil { | ||||
| 		p.logger.Errorf("Error obtaining redirect: %v", err) | ||||
| 		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	redirectURI := p.GetRedirectURI(req.Host) | ||||
| 	http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), http.StatusFound) | ||||
| } | ||||
|  | ||||
| // OAuthCallback is the OAuth2 authentication flow callback that finishes the | ||||
| // OAuth2 authentication flow | ||||
| func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { | ||||
| 	// finish the oauth cycle | ||||
| 	err := req.ParseForm() | ||||
| 	if err != nil { | ||||
| 		p.logger.Errorf("Error while parsing OAuth2 callback: %v", err) | ||||
| 		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	errorString := req.Form.Get("error") | ||||
| 	if errorString != "" { | ||||
| 		p.logger.Errorf("Error while parsing OAuth2 callback: %s", errorString) | ||||
| 		p.ErrorPage(rw, http.StatusForbidden, "Permission Denied", errorString) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	session, err := p.redeemCode(req.Context(), req.Host, req.Form.Get("code")) | ||||
| 	if err != nil { | ||||
| 		p.logger.Errorf("Error redeeming code during OAuth2 callback: %v", err) | ||||
| 		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", "Internal Error") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	s := strings.SplitN(req.Form.Get("state"), ":", 2) | ||||
| 	if len(s) != 2 { | ||||
| 		p.logger.Error("Error while parsing OAuth2 state: invalid length") | ||||
| 		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", "Invalid State") | ||||
| 		return | ||||
| 	} | ||||
| 	nonce := s[0] | ||||
| 	redirect := s[1] | ||||
| 	c, err := req.Cookie(p.CSRFCookieName) | ||||
| 	if err != nil { | ||||
| 		p.logger.WithField("user", session.Email).WithField("status", "AuthFailure").Errorf("Invalid authentication via OAuth2: unable to obtain CSRF cookie") | ||||
| 		p.ErrorPage(rw, http.StatusForbidden, "Permission Denied", err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	p.ClearCSRFCookie(rw, req) | ||||
| 	if c.Value != nonce { | ||||
| 		p.logger.WithField("is", c.Value).WithField("should", nonce).WithField("user", session.Email).WithField("status", "AuthFailure").Errorf("Invalid authentication via OAuth2: CSRF token mismatch, potential attack") | ||||
| 		p.ErrorPage(rw, http.StatusForbidden, "Permission Denied", "CSRF Failed") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !p.IsValidRedirect(redirect) { | ||||
| 		redirect = "/" | ||||
| 	} | ||||
|  | ||||
| 	// set cookie, or deny | ||||
| 	if p.provider.ValidateGroup(session.Email) { | ||||
| 		p.logger.WithField("user", session.Email).WithField("status", "AuthFailure").Infof("Authenticated via OAuth2: %s", session) | ||||
| 		err := p.SaveSession(rw, req, session) | ||||
| 		if err != nil { | ||||
| 			p.logger.Errorf("Error saving session state for client %v", err) | ||||
| 			p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error()) | ||||
| 			return | ||||
| 		} | ||||
| 		http.Redirect(rw, req, redirect, http.StatusFound) | ||||
| 	} else { | ||||
| 		p.logger.WithField("user", session.Email).WithField("status", "AuthFailure").Errorf("Invalid authentication via OAuth2: unauthorized") | ||||
| 		p.ErrorPage(rw, http.StatusForbidden, "Permission Denied", "Invalid Account") | ||||
| 	} | ||||
| } | ||||
| @ -1,507 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	b64 "encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coreos/go-oidc" | ||||
| 	"github.com/justinas/alice" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||
| 	sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/middleware" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/upstream" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/providers" | ||||
| 	"goauthentik.io/api" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| 	staticWeb "goauthentik.io/web" | ||||
|  | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	httpScheme  = "http" | ||||
| 	httpsScheme = "https" | ||||
|  | ||||
| 	applicationJSON = "application/json" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// ErrNeedsLogin means the user should be redirected to the login page | ||||
| 	ErrNeedsLogin = errors.New("redirect to login page") | ||||
|  | ||||
| 	// Used to check final redirects are not susceptible to open redirects. | ||||
| 	// Matches //, /\ and both of these with whitespace in between (eg / / or / \). | ||||
| 	invalidRedirectRegex = regexp.MustCompile(`[/\\](?:[\s\v]*|\.{1,2})[/\\]`) | ||||
| ) | ||||
|  | ||||
| // OAuthProxy is the main authentication proxy | ||||
| type OAuthProxy struct { | ||||
| 	client *http.Client | ||||
|  | ||||
| 	CookieSeed     string | ||||
| 	CookieName     string | ||||
| 	CSRFCookieName string | ||||
| 	CookieDomains  []string | ||||
| 	CookiePath     string | ||||
| 	CookieSecure   bool | ||||
| 	CookieHTTPOnly bool | ||||
| 	CookieExpire   time.Duration | ||||
| 	CookieRefresh  time.Duration | ||||
| 	CookieSameSite string | ||||
|  | ||||
| 	RobotsPath        string | ||||
| 	SignInPath        string | ||||
| 	SignOutPath       string | ||||
| 	OAuthStartPath    string | ||||
| 	OAuthCallbackPath string | ||||
| 	AuthOnlyPath      string | ||||
| 	UserInfoPath      string | ||||
|  | ||||
| 	endSessionEndpoint         string | ||||
| 	mode                       api.ProxyMode | ||||
| 	BasicAuthUserAttribute     string | ||||
| 	BasicAuthPasswordAttribute string | ||||
| 	ExternalHost               string | ||||
|  | ||||
| 	redirectURL             *url.URL // the url to receive requests at | ||||
| 	whitelistDomains        []string | ||||
| 	provider                providers.Provider | ||||
| 	sessionStore            sessionsapi.SessionStore | ||||
| 	ProxyPrefix             string | ||||
| 	serveMux                http.Handler | ||||
| 	SetXAuthRequest         bool | ||||
| 	SetBasicAuth            bool | ||||
| 	PassUserHeaders         bool | ||||
| 	PassAccessToken         bool | ||||
| 	SetAuthorization        bool | ||||
| 	PassAuthorization       bool | ||||
| 	PreferEmailToUser       bool | ||||
| 	skipAuthRegex           []string | ||||
| 	skipAuthPreflight       bool | ||||
| 	skipAuthStripHeaders    bool | ||||
| 	mainJwtBearerVerifier   *oidc.IDTokenVerifier | ||||
| 	extraJwtBearerVerifiers []*oidc.IDTokenVerifier | ||||
| 	compiledRegex           []*regexp.Regexp | ||||
| 	templates               *template.Template | ||||
|  | ||||
| 	sessionChain alice.Chain | ||||
|  | ||||
| 	logger *log.Entry | ||||
| } | ||||
|  | ||||
| // NewOAuthProxy creates a new instance of OAuthProxy from the options provided | ||||
| func NewOAuthProxy(opts *options.Options, provider api.ProxyOutpostConfig, c *http.Client) (*OAuthProxy, error) { | ||||
| 	logger := log.WithField("logger", "authentik.outpost.proxy").WithField("provider", provider.Name) | ||||
| 	sessionStore, err := sessions.NewSessionStore(&opts.Session, &opts.Cookie) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error initialising session store: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	templates := getTemplates() | ||||
| 	proxyErrorHandler := upstream.NewProxyErrorHandler(templates.Lookup("error.html"), opts.ProxyPrefix) | ||||
| 	upstreamProxy, err := upstream.NewProxy(opts.UpstreamServers, opts.GetSignatureData(), proxyErrorHandler) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error initialising upstream proxy: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	for _, u := range opts.GetCompiledRegex() { | ||||
| 		logger.Printf("compiled skip-auth-regex => %q", u) | ||||
| 	} | ||||
|  | ||||
| 	redirectURL := opts.GetRedirectURL() | ||||
| 	if redirectURL.Path == "" { | ||||
| 		redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix) | ||||
| 	} | ||||
|  | ||||
| 	logger.WithField("auth_url", opts.GetProvider().Data().LoginURL.String()).WithField("client_id", opts.ClientID).Info("proxy instance configured") | ||||
|  | ||||
| 	sessionChain := buildSessionChain(opts, sessionStore) | ||||
|  | ||||
| 	return &OAuthProxy{ | ||||
| 		client:         c, | ||||
| 		CookieName:     opts.Cookie.Name, | ||||
| 		CSRFCookieName: fmt.Sprintf("%v_%v", opts.Cookie.Name, "csrf"), | ||||
| 		CookieSeed:     opts.Cookie.Secret, | ||||
| 		CookieDomains:  opts.Cookie.Domains, | ||||
| 		CookiePath:     opts.Cookie.Path, | ||||
| 		CookieSecure:   opts.Cookie.Secure, | ||||
| 		CookieHTTPOnly: opts.Cookie.HTTPOnly, | ||||
| 		CookieExpire:   opts.Cookie.Expire, | ||||
| 		CookieRefresh:  opts.Cookie.Refresh, | ||||
| 		CookieSameSite: opts.Cookie.SameSite, | ||||
|  | ||||
| 		mode:              *provider.Mode, | ||||
| 		RobotsPath:        "/robots.txt", | ||||
| 		SignInPath:        fmt.Sprintf("%s/sign_in", opts.ProxyPrefix), | ||||
| 		SignOutPath:       fmt.Sprintf("%s/sign_out", opts.ProxyPrefix), | ||||
| 		OAuthStartPath:    fmt.Sprintf("%s/start", opts.ProxyPrefix), | ||||
| 		OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix), | ||||
| 		AuthOnlyPath:      fmt.Sprintf("%s/auth", opts.ProxyPrefix), | ||||
| 		UserInfoPath:      fmt.Sprintf("%s/userinfo", opts.ProxyPrefix), | ||||
|  | ||||
| 		ProxyPrefix:             opts.ProxyPrefix, | ||||
| 		provider:                opts.GetProvider(), | ||||
| 		sessionStore:            sessionStore, | ||||
| 		serveMux:                upstreamProxy, | ||||
| 		redirectURL:             redirectURL, | ||||
| 		whitelistDomains:        opts.WhitelistDomains, | ||||
| 		skipAuthRegex:           opts.SkipAuthRegex, | ||||
| 		skipAuthPreflight:       opts.SkipAuthPreflight, | ||||
| 		skipAuthStripHeaders:    opts.SkipAuthStripHeaders, | ||||
| 		mainJwtBearerVerifier:   opts.GetOIDCVerifier(), | ||||
| 		extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(), | ||||
| 		compiledRegex:           opts.GetCompiledRegex(), | ||||
| 		SetXAuthRequest:         opts.SetXAuthRequest, | ||||
| 		SetBasicAuth:            opts.SetBasicAuth, | ||||
| 		PassUserHeaders:         opts.PassUserHeaders, | ||||
| 		PassAccessToken:         opts.PassAccessToken, | ||||
| 		SetAuthorization:        opts.SetAuthorization, | ||||
| 		PassAuthorization:       opts.PassAuthorization, | ||||
| 		PreferEmailToUser:       opts.PreferEmailToUser, | ||||
| 		templates:               templates, | ||||
|  | ||||
| 		sessionChain: sessionChain, | ||||
|  | ||||
| 		logger: logger, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionStore) alice.Chain { | ||||
| 	chain := alice.New(middleware.NewScope()) | ||||
|  | ||||
| 	chain = chain.Append(middleware.NewStoredSessionLoader(&middleware.StoredSessionLoaderOptions{ | ||||
| 		SessionStore:           sessionStore, | ||||
| 		RefreshPeriod:          opts.Cookie.Refresh, | ||||
| 		RefreshSessionIfNeeded: opts.GetProvider().RefreshSessionIfNeeded, | ||||
| 		ValidateSessionState:   opts.GetProvider().ValidateSessionState, | ||||
| 	})) | ||||
|  | ||||
| 	return chain | ||||
| } | ||||
|  | ||||
| // RobotsTxt disallows scraping pages from the OAuthProxy | ||||
| func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) { | ||||
| 	_, err := fmt.Fprintf(rw, "User-agent: *\nDisallow: /") | ||||
| 	if err != nil { | ||||
| 		p.logger.Errorf("Error writing robots.txt: %v", err) | ||||
| 		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	rw.WriteHeader(http.StatusOK) | ||||
| } | ||||
|  | ||||
| // ErrorPage writes an error response | ||||
| func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) { | ||||
| 	rw.WriteHeader(code) | ||||
| 	t := struct { | ||||
| 		Title       string | ||||
| 		Message     string | ||||
| 		ProxyPrefix string | ||||
| 	}{ | ||||
| 		Title:       fmt.Sprintf("%d %s", code, title), | ||||
| 		Message:     message, | ||||
| 		ProxyPrefix: p.ProxyPrefix, | ||||
| 	} | ||||
| 	err := p.templates.ExecuteTemplate(rw, "error.html", t) | ||||
| 	if err != nil { | ||||
| 		p.logger.Printf("Error rendering error.html template: %v", err) | ||||
| 		http.Error(rw, "Internal Server Error", http.StatusInternalServerError) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en | ||||
| var noCacheHeaders = map[string]string{ | ||||
| 	"Expires":         time.Unix(0, 0).Format(time.RFC1123), | ||||
| 	"Cache-Control":   "no-cache, no-store, must-revalidate, max-age=0", | ||||
| 	"X-Accel-Expires": "0", // https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/ | ||||
| } | ||||
|  | ||||
| // prepareNoCache prepares headers for preventing browser caching. | ||||
| func prepareNoCache(w http.ResponseWriter) { | ||||
| 	// Set NoCache headers | ||||
| 	for k, v := range noCacheHeaders { | ||||
| 		w.Header().Set(k, v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { | ||||
| 	if req.URL.Path != p.AuthOnlyPath && strings.HasPrefix(req.URL.Path, p.ProxyPrefix) { | ||||
| 		prepareNoCache(rw) | ||||
| 	} | ||||
| 	rw.Header().Set("Server", "authentik-outpost") | ||||
|  | ||||
| 	switch path := req.URL.Path; { | ||||
| 	case path == p.RobotsPath: | ||||
| 		p.RobotsTxt(rw) | ||||
| 	case p.IsWhitelistedRequest(req): | ||||
| 		p.SkipAuthProxy(rw, req) | ||||
| 	case path == p.SignInPath: | ||||
| 		p.OAuthStart(rw, req) | ||||
| 	case path == p.SignOutPath: | ||||
| 		p.SignOut(rw, req) | ||||
| 	case path == p.OAuthStartPath: | ||||
| 		p.OAuthStart(rw, req) | ||||
| 	case path == p.OAuthCallbackPath: | ||||
| 		p.OAuthCallback(rw, req) | ||||
| 	case path == p.AuthOnlyPath: | ||||
| 		p.AuthenticateOnly(rw, req) | ||||
| 	case path == p.UserInfoPath: | ||||
| 		p.UserInfo(rw, req) | ||||
| 	case strings.HasPrefix(path, fmt.Sprintf("%s/static", p.ProxyPrefix)): | ||||
| 		p.ServeStatic(rw, req) | ||||
| 	default: | ||||
| 		p.Proxy(rw, req) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *OAuthProxy) ServeStatic(rw http.ResponseWriter, req *http.Request) { | ||||
| 	staticFs := http.FileServer(http.FS(staticWeb.StaticDist)) | ||||
| 	http.StripPrefix(fmt.Sprintf("%s/static", p.ProxyPrefix), staticFs).ServeHTTP(rw, req) | ||||
| } | ||||
|  | ||||
| //UserInfo endpoint outputs session email and preferred username in JSON format | ||||
| func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) { | ||||
|  | ||||
| 	session, err := p.getAuthenticatedSession(rw, req) | ||||
| 	if err != nil { | ||||
| 		http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) | ||||
| 		return | ||||
| 	} | ||||
| 	userInfo := struct { | ||||
| 		Email             string `json:"email"` | ||||
| 		PreferredUsername string `json:"preferredUsername,omitempty"` | ||||
| 	}{ | ||||
| 		Email:             session.Email, | ||||
| 		PreferredUsername: session.PreferredUsername, | ||||
| 	} | ||||
| 	rw.Header().Set("Content-Type", "application/json") | ||||
| 	rw.WriteHeader(http.StatusOK) | ||||
| 	err = json.NewEncoder(rw).Encode(userInfo) | ||||
| 	if err != nil { | ||||
| 		p.logger.Errorf("Error encoding user info: %v", err) | ||||
| 		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SignOut sends a response to clear the authentication cookie | ||||
| func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) { | ||||
| 	err := p.ClearSessionCookie(rw, req) | ||||
| 	if err != nil { | ||||
| 		p.logger.Errorf("Error clearing session cookie: %v", err) | ||||
| 		p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	http.Redirect(rw, req, p.endSessionEndpoint, http.StatusFound) | ||||
| } | ||||
|  | ||||
| // AuthenticateOnly checks whether the user is currently logged in | ||||
| func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) { | ||||
| 	session, err := p.getAuthenticatedSession(rw, req) | ||||
| 	if err != nil { | ||||
| 		if p.mode == api.PROXYMODE_FORWARD_SINGLE || p.mode == api.PROXYMODE_FORWARD_DOMAIN { | ||||
| 			if _, ok := req.URL.Query()["nginx"]; ok { | ||||
| 				rw.WriteHeader(401) | ||||
| 				return | ||||
| 			} | ||||
| 			if _, ok := req.URL.Query()["traefik"]; ok { | ||||
| 				host := "" | ||||
| 				// Optional suffix, which is appended to the URL | ||||
| 				suffix := "" | ||||
| 				if p.mode == api.PROXYMODE_FORWARD_SINGLE { | ||||
| 					host = web.GetHost(req) | ||||
| 				} else if p.mode == api.PROXYMODE_FORWARD_DOMAIN { | ||||
| 					host = p.ExternalHost | ||||
| 					// set the ?rd flag to the current URL we have, since we redirect | ||||
| 					// to a (possibly) different domain, but we want to be redirected back | ||||
| 					// to the application | ||||
| 					v := url.Values{ | ||||
| 						// see https://doc.traefik.io/traefik/middlewares/forwardauth/ | ||||
| 						// X-Forwarded-Uri is only the path, so we need to build the entire URL | ||||
| 						"rd": []string{fmt.Sprintf( | ||||
| 							"%s://%s%s", | ||||
| 							req.Header.Get("X-Forwarded-Proto"), | ||||
| 							req.Header.Get("X-Forwarded-Host"), | ||||
| 							req.Header.Get("X-Forwarded-Uri"), | ||||
| 						)}, | ||||
| 					} | ||||
| 					suffix = fmt.Sprintf("?%s", v.Encode()) | ||||
| 				} | ||||
| 				proto := req.Header.Get("X-Forwarded-Proto") | ||||
| 				if proto != "" { | ||||
| 					proto = proto + ":" | ||||
| 				} | ||||
| 				rdFinal := fmt.Sprintf("%s//%s%s%s", proto, host, p.OAuthStartPath, suffix) | ||||
| 				p.logger.WithField("url", rdFinal).Debug("Redirecting to login") | ||||
| 				http.Redirect(rw, req, rdFinal, http.StatusTemporaryRedirect) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		http.Error(rw, "unauthorized request", http.StatusUnauthorized) | ||||
| 		return | ||||
| 	} | ||||
| 	// we are authenticated | ||||
| 	p.addHeadersForProxying(rw, req, session) | ||||
| 	if p.mode == api.PROXYMODE_FORWARD_SINGLE || p.mode == api.PROXYMODE_FORWARD_DOMAIN { | ||||
| 		for headerKey, headers := range req.Header { | ||||
| 			for _, value := range headers { | ||||
| 				rw.Header().Set(headerKey, value) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	rw.WriteHeader(http.StatusAccepted) | ||||
| } | ||||
|  | ||||
| // SkipAuthProxy proxies whitelisted requests and skips authentication | ||||
| func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) { | ||||
| 	if p.skipAuthStripHeaders { | ||||
| 		p.stripAuthHeaders(req) | ||||
| 	} | ||||
| 	p.serveMux.ServeHTTP(rw, req) | ||||
| } | ||||
|  | ||||
| // Proxy proxies the user request if the user is authenticated else it prompts | ||||
| // them to authenticate | ||||
| func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) { | ||||
| 	session, err := p.getAuthenticatedSession(rw, req) | ||||
| 	switch err { | ||||
| 	case nil: | ||||
| 		// we are authenticated | ||||
| 		p.addHeadersForProxying(rw, req, session) | ||||
| 		p.serveMux.ServeHTTP(rw, req) | ||||
|  | ||||
| 	case ErrNeedsLogin: | ||||
| 		// we need to send the user to a login screen | ||||
| 		if isAjax(req) { | ||||
| 			// no point redirecting an AJAX request | ||||
| 			p.ErrorJSON(rw, http.StatusUnauthorized) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		p.OAuthStart(rw, req) | ||||
|  | ||||
| 	default: | ||||
| 		// unknown error | ||||
| 		p.logger.Errorf("Unexpected internal error: %v", err) | ||||
| 		p.ErrorPage(rw, http.StatusInternalServerError, | ||||
| 			"Internal Error", "Internal Error") | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| // getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so | ||||
| // Returns nil, ErrNeedsLogin if user needs to login. | ||||
| // Set-Cookie headers may be set on the response as a side-effect of calling this method. | ||||
| func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.Request) (*sessionsapi.SessionState, error) { | ||||
| 	var session *sessionsapi.SessionState | ||||
|  | ||||
| 	getSession := p.sessionChain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { | ||||
| 		session = middleware.GetRequestScope(req).Session | ||||
| 	})) | ||||
| 	getSession.ServeHTTP(rw, req) | ||||
|  | ||||
| 	if session == nil { | ||||
| 		return nil, ErrNeedsLogin | ||||
| 	} | ||||
|  | ||||
| 	return session, nil | ||||
| } | ||||
|  | ||||
| // addHeadersForProxying adds the appropriate headers the request / response for proxying | ||||
| func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) { | ||||
| 	// req is the request that is forwarded to the upstream server | ||||
| 	// rw is the response writer that goes back to the client | ||||
| 	req.Header["X-Forwarded-User"] = []string{session.User} | ||||
| 	if session.Email != "" { | ||||
| 		req.Header["X-Forwarded-Email"] = []string{session.Email} | ||||
| 	} | ||||
|  | ||||
| 	if session.PreferredUsername != "" { | ||||
| 		req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername} | ||||
| 		req.Header["X-Auth-Username"] = []string{session.PreferredUsername} | ||||
| 	} else { | ||||
| 		req.Header.Del("X-Forwarded-Preferred-Username") | ||||
| 		req.Header.Del("X-Auth-Username") | ||||
| 	} | ||||
|  | ||||
| 	claims := Claims{} | ||||
| 	err := claims.FromIDToken(session.IDToken) | ||||
| 	if err != nil { | ||||
| 		log.WithError(err).Warning("Failed to parse IDToken") | ||||
| 	} | ||||
| 	// Set groups in header | ||||
| 	groups := strings.Join(claims.Groups, "|") | ||||
| 	req.Header["X-Auth-Groups"] = []string{groups} | ||||
|  | ||||
| 	userAttributes := claims.Proxy.UserAttributes | ||||
| 	// Attempt to set basic auth based on user's attributes | ||||
| 	if p.SetBasicAuth { | ||||
| 		var ok bool | ||||
| 		var password string | ||||
| 		if password, ok = userAttributes[p.BasicAuthPasswordAttribute].(string); !ok { | ||||
| 			password = "" | ||||
| 		} | ||||
| 		// Check if we should use email or a custom attribute as username | ||||
| 		var username string | ||||
| 		if username, ok = userAttributes[p.BasicAuthUserAttribute].(string); !ok { | ||||
| 			username = session.Email | ||||
| 		} | ||||
| 		authVal := b64.StdEncoding.EncodeToString([]byte(username + ":" + password)) | ||||
| 		p.logger.WithField("username", username).Trace("setting http basic auth") | ||||
| 		req.Header["Authorization"] = []string{fmt.Sprintf("Basic %s", authVal)} | ||||
| 	} | ||||
| 	// Check if user has additional headers set that we should sent | ||||
| 	if additionalHeaders, ok := userAttributes["additionalHeaders"].(map[string]interface{}); ok { | ||||
| 		p.logger.WithField("headers", additionalHeaders).Trace("setting additional headers") | ||||
| 		if additionalHeaders == nil { | ||||
| 			return | ||||
| 		} | ||||
| 		for key, value := range additionalHeaders { | ||||
| 			req.Header.Set(key, toString(value)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // stripAuthHeaders removes Auth headers for whitelisted routes from skipAuthRegex | ||||
| func (p *OAuthProxy) stripAuthHeaders(req *http.Request) { | ||||
| 	if p.PassUserHeaders { | ||||
| 		req.Header.Del("X-Forwarded-User") | ||||
| 		req.Header.Del("X-Auth-Groups") | ||||
| 		req.Header.Del("X-Forwarded-Email") | ||||
| 		req.Header.Del("X-Forwarded-Preferred-Username") | ||||
| 	} | ||||
|  | ||||
| 	if p.PassAccessToken { | ||||
| 		req.Header.Del("X-Forwarded-Access-Token") | ||||
| 	} | ||||
|  | ||||
| 	if p.PassAuthorization { | ||||
| 		req.Header.Del("Authorization") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // isAjax checks if a request is an ajax request | ||||
| func isAjax(req *http.Request) bool { | ||||
| 	acceptValues := req.Header.Values("Accept") | ||||
| 	const ajaxReq = applicationJSON | ||||
| 	for _, v := range acceptValues { | ||||
| 		if v == ajaxReq { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // ErrorJSON returns the error code with an application/json mime type | ||||
| func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) { | ||||
| 	rw.Header().Set("Content-Type", applicationJSON) | ||||
| 	rw.WriteHeader(code) | ||||
| } | ||||
| @ -1,137 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/pires/go-proxyproto" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/internal/crypto" | ||||
| 	"goauthentik.io/internal/outpost/ak" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| ) | ||||
|  | ||||
| // Server represents an HTTP server | ||||
| type Server struct { | ||||
| 	Handlers map[string]*providerBundle | ||||
| 	Listen   string | ||||
|  | ||||
| 	stop        chan struct{} // channel for waiting shutdown | ||||
| 	logger      *log.Entry | ||||
| 	ak          *ak.APIController | ||||
| 	cs          *ak.CryptoStore | ||||
| 	defaultCert tls.Certificate | ||||
| } | ||||
|  | ||||
| // NewServer initialise a new HTTP Server | ||||
| func NewServer(ac *ak.APIController) *Server { | ||||
| 	defaultCert, err := crypto.GenerateSelfSignedCert() | ||||
| 	if err != nil { | ||||
| 		log.Warning(err) | ||||
| 	} | ||||
| 	return &Server{ | ||||
| 		Handlers:    make(map[string]*providerBundle), | ||||
| 		Listen:      "0.0.0.0:%d", | ||||
| 		logger:      log.WithField("logger", "authentik.outpost.proxy-http-server"), | ||||
| 		defaultCert: defaultCert, | ||||
| 		ak:          ac, | ||||
| 		cs:          ak.NewCryptoStore(ac.Client.CryptoApi), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ServeHTTP constructs a net.Listener and starts handling HTTP requests | ||||
| func (s *Server) ServeHTTP() { | ||||
| 	listenAddress := fmt.Sprintf(s.Listen, 4180) | ||||
| 	listener, err := net.Listen("tcp", listenAddress) | ||||
| 	if err != nil { | ||||
| 		s.logger.Fatalf("FATAL: listen (%s) failed - %s", listenAddress, err) | ||||
| 	} | ||||
| 	proxyListener := &proxyproto.Listener{Listener: listener} | ||||
| 	defer proxyListener.Close() | ||||
|  | ||||
| 	s.logger.Printf("listening on %s", listener.Addr()) | ||||
| 	s.serve(proxyListener) | ||||
| 	s.logger.Printf("closing %s", listener.Addr()) | ||||
| } | ||||
|  | ||||
| func (s *Server) TimerFlowCacheExpiry() {} | ||||
|  | ||||
| func (s *Server) Handler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if r.URL.Path == "/akprox/ping" { | ||||
| 		w.WriteHeader(204) | ||||
| 		return | ||||
| 	} | ||||
| 	host := web.GetHost(r) | ||||
| 	handler, ok := s.Handlers[host] | ||||
| 	if !ok { | ||||
| 		// If we only have one handler, host name switching doesn't matter | ||||
| 		if len(s.Handlers) == 1 { | ||||
| 			for k := range s.Handlers { | ||||
| 				s.Handlers[k].ServeHTTP(w, r) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		// Get a list of all host keys we know | ||||
| 		hostKeys := make([]string, 0, len(s.Handlers)) | ||||
| 		for k := range s.Handlers { | ||||
| 			hostKeys = append(hostKeys, k) | ||||
| 		} | ||||
| 		s.logger.WithField("host", host).WithField("known-hosts", strings.Join(hostKeys, ",")).Debug("Host header does not match any we know of") | ||||
| 		w.WriteHeader(404) | ||||
| 		return | ||||
| 	} | ||||
| 	handler.ServeHTTP(w, r) | ||||
| } | ||||
|  | ||||
| func (s *Server) serve(listener net.Listener) { | ||||
| 	srv := &http.Server{Handler: http.HandlerFunc(s.Handler)} | ||||
|  | ||||
| 	// See https://golang.org/pkg/net/http/#Server.Shutdown | ||||
| 	idleConnsClosed := make(chan struct{}) | ||||
| 	go func() { | ||||
| 		<-s.stop // wait notification for stopping server | ||||
|  | ||||
| 		// We received an interrupt signal, shut down. | ||||
| 		if err := srv.Shutdown(context.Background()); err != nil { | ||||
| 			// Error from closing listeners, or context timeout: | ||||
| 			s.logger.Printf("HTTP server Shutdown: %v", err) | ||||
| 		} | ||||
| 		close(idleConnsClosed) | ||||
| 	}() | ||||
|  | ||||
| 	err := srv.Serve(listener) | ||||
| 	if err != nil && !errors.Is(err, http.ErrServerClosed) { | ||||
| 		s.logger.Errorf("ERROR: http.Serve() - %s", err) | ||||
| 	} | ||||
| 	<-idleConnsClosed | ||||
| } | ||||
|  | ||||
| // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted | ||||
| // connections. It's used by ListenAndServe and ListenAndServeTLS so | ||||
| // dead TCP connections (e.g. closing laptop mid-download) eventually | ||||
| // go away. | ||||
| type tcpKeepAliveListener struct { | ||||
| 	*net.TCPListener | ||||
| } | ||||
|  | ||||
| func (ln tcpKeepAliveListener) Accept() (net.Conn, error) { | ||||
| 	tc, err := ln.AcceptTCP() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	err = tc.SetKeepAlive(true) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Error setting Keep-Alive: %v", err) | ||||
| 	} | ||||
| 	err = tc.SetKeepAlivePeriod(3 * time.Minute) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Error setting Keep-Alive period: %v", err) | ||||
| 	} | ||||
| 	return tc, nil | ||||
| } | ||||
| @ -1,62 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/pires/go-proxyproto" | ||||
| ) | ||||
|  | ||||
| func (s *Server) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||||
| 	handler, ok := s.Handlers[info.ServerName] | ||||
| 	if !ok { | ||||
| 		s.logger.WithField("server-name", info.ServerName).Debug("Handler does not exist") | ||||
| 		return &s.defaultCert, nil | ||||
| 	} | ||||
| 	if handler.cert == nil { | ||||
| 		s.logger.WithField("server-name", info.ServerName).Debug("Handler does not have a certificate") | ||||
| 		return &s.defaultCert, nil | ||||
| 	} | ||||
| 	return handler.cert, nil | ||||
| } | ||||
|  | ||||
| // ServeHTTPS constructs a net.Listener and starts handling HTTPS requests | ||||
| func (s *Server) ServeHTTPS() { | ||||
| 	listenAddress := fmt.Sprintf(s.Listen, 4443) | ||||
| 	config := &tls.Config{ | ||||
| 		MinVersion:     tls.VersionTLS12, | ||||
| 		MaxVersion:     tls.VersionTLS12, | ||||
| 		GetCertificate: s.getCertificates, | ||||
| 	} | ||||
|  | ||||
| 	ln, err := net.Listen("tcp", listenAddress) | ||||
| 	if err != nil { | ||||
| 		s.logger.Fatalf("FATAL: listen (%s) failed - %s", listenAddress, err) | ||||
| 	} | ||||
| 	s.logger.Printf("listening on %s", ln.Addr()) | ||||
|  | ||||
| 	proxyListener := &proxyproto.Listener{Listener: tcpKeepAliveListener{ln.(*net.TCPListener)}} | ||||
| 	defer proxyListener.Close() | ||||
|  | ||||
| 	tlsListener := tls.NewListener(proxyListener, config) | ||||
| 	s.serve(tlsListener) | ||||
| 	s.logger.Printf("closing %s", tlsListener.Addr()) | ||||
| } | ||||
|  | ||||
| func (s *Server) Start() error { | ||||
| 	wg := sync.WaitGroup{} | ||||
| 	wg.Add(2) | ||||
| 	go func() { | ||||
| 		defer wg.Done() | ||||
| 		s.logger.Debug("Starting HTTP Server...") | ||||
| 		s.ServeHTTP() | ||||
| 	}() | ||||
| 	go func() { | ||||
| 		defer wg.Done() | ||||
| 		s.logger.Debug("Starting HTTPs Server...") | ||||
| 		s.ServeHTTPS() | ||||
| 	}() | ||||
| 	return nil | ||||
| } | ||||
| @ -1,16 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"html/template" | ||||
|  | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/internal/outpost/proxy/templates" | ||||
| ) | ||||
|  | ||||
| func getTemplates() *template.Template { | ||||
| 	t, err := template.New("foo").Parse(templates.ErrorTemplate) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("failed parsing template %s", err) | ||||
| 	} | ||||
| 	return t | ||||
| } | ||||
| @ -1,6 +0,0 @@ | ||||
| package templates | ||||
|  | ||||
| import _ "embed" | ||||
|  | ||||
| //go:embed error.html | ||||
| var ErrorTemplate string | ||||
| @ -1,18 +0,0 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // toString Generic to string function, currently supports actual strings and integers | ||||
| func toString(in interface{}) string { | ||||
| 	switch v := in.(type) { | ||||
| 	case string: | ||||
| 		return v | ||||
| 	case *string: | ||||
| 		return *v | ||||
| 	case int: | ||||
| 		return strconv.Itoa(v) | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
							
								
								
									
										208
									
								
								internal/outpost/proxyv2/application/application.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								internal/outpost/proxyv2/application/application.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,208 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"encoding/gob" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coreos/go-oidc" | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/gorilla/sessions" | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/api" | ||||
| 	"goauthentik.io/internal/outpost/ak" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/constants" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/hs256" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/metrics" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| 	"golang.org/x/oauth2" | ||||
| ) | ||||
|  | ||||
| type Application struct { | ||||
| 	Host                 string | ||||
| 	Cert                 *tls.Certificate | ||||
| 	UnauthenticatedRegex []*regexp.Regexp | ||||
|  | ||||
| 	oauthConfig   oauth2.Config | ||||
| 	tokenVerifier *oidc.IDTokenVerifier | ||||
|  | ||||
| 	sessions    sessions.Store | ||||
| 	proxyConfig api.ProxyOutpostConfig | ||||
| 	httpClient  *http.Client | ||||
|  | ||||
| 	log *log.Entry | ||||
| 	mux *mux.Router | ||||
| } | ||||
|  | ||||
| func akProviderToEndpoint(p api.ProxyOutpostConfig) oauth2.Endpoint { | ||||
| 	authUrl := p.OidcConfiguration.AuthorizationEndpoint | ||||
| 	if browserHost, found := os.LookupEnv("AUTHENTIK_HOST_BROWSER"); found { | ||||
| 		host := os.Getenv("AUTHENTIK_HOST") | ||||
| 		authUrl = strings.ReplaceAll(authUrl, host, browserHost) | ||||
| 	} | ||||
| 	return oauth2.Endpoint{ | ||||
| 		AuthURL:   authUrl, | ||||
| 		TokenURL:  p.OidcConfiguration.TokenEndpoint, | ||||
| 		AuthStyle: oauth2.AuthStyleInParams, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore) *Application { | ||||
| 	gob.Register(Claims{}) | ||||
|  | ||||
| 	externalHost, err := url.Parse(p.ExternalHost) | ||||
| 	if err != nil { | ||||
| 		log.WithError(err).Warning("Failed to parse URL, skipping provider") | ||||
| 	} | ||||
|  | ||||
| 	// Support for RS256, new proxy providers will use HS256 but old ones | ||||
| 	// might not, and this makes testing easier | ||||
| 	var ks oidc.KeySet | ||||
| 	if contains(p.OidcConfiguration.IdTokenSigningAlgValuesSupported, "HS256") { | ||||
| 		ks = hs256.NewKeySet(*p.ClientSecret) | ||||
| 	} else { | ||||
| 		ctx := context.WithValue(context.Background(), oauth2.HTTPClient, c) | ||||
| 		oidc.NewRemoteKeySet(ctx, p.OidcConfiguration.JwksUri) | ||||
| 	} | ||||
|  | ||||
| 	var verifier = oidc.NewVerifier(p.OidcConfiguration.Issuer, ks, &oidc.Config{ | ||||
| 		ClientID:             *p.ClientId, | ||||
| 		SupportedSigningAlgs: []string{"HS256"}, | ||||
| 	}) | ||||
|  | ||||
| 	// Configure an OpenID Connect aware OAuth2 client. | ||||
| 	oauth2Config := oauth2.Config{ | ||||
| 		ClientID:     *p.ClientId, | ||||
| 		ClientSecret: *p.ClientSecret, | ||||
| 		RedirectURL:  fmt.Sprintf("%s/akprox/callback", p.ExternalHost), | ||||
| 		Endpoint:     akProviderToEndpoint(p), | ||||
| 		Scopes:       []string{oidc.ScopeOpenID, "profile", "email", "ak_proxy"}, | ||||
| 	} | ||||
| 	mux := mux.NewRouter() | ||||
| 	a := &Application{ | ||||
| 		Host:          externalHost.Host, | ||||
| 		log:           log.WithField("logger", "authentik.outpost.proxy.bundle").WithField("provider", p.Name), | ||||
| 		oauthConfig:   oauth2Config, | ||||
| 		tokenVerifier: verifier, | ||||
| 		sessions:      GetStore(p), | ||||
| 		proxyConfig:   p, | ||||
| 		httpClient:    c, | ||||
| 		mux:           mux, | ||||
| 	} | ||||
| 	muxLogger := log.WithField("logger", "authentik.outpost.proxyv2.application").WithField("name", p.Name) | ||||
| 	mux.Use(web.NewLoggingHandler(muxLogger, func(l *log.Entry, r *http.Request) *log.Entry { | ||||
| 		s, err := a.sessions.Get(r, constants.SeesionName) | ||||
| 		if err != nil { | ||||
| 			return l | ||||
| 		} | ||||
| 		claims, ok := s.Values[constants.SessionClaims] | ||||
| 		if claims == nil || !ok { | ||||
| 			return l | ||||
| 		} | ||||
| 		c, ok := claims.(Claims) | ||||
| 		if !ok { | ||||
| 			return l | ||||
| 		} | ||||
| 		return l.WithField("request_username", c.Email) | ||||
| 	})) | ||||
| 	mux.Use(func(inner http.Handler) http.Handler { | ||||
| 		return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 			c, _ := a.getClaims(r) | ||||
| 			user := "" | ||||
| 			if c != nil { | ||||
| 				user = c.Email | ||||
| 			} | ||||
| 			before := time.Now() | ||||
| 			inner.ServeHTTP(rw, r) | ||||
| 			after := time.Since(before) | ||||
| 			metrics.Requests.With(prometheus.Labels{ | ||||
| 				"type":   "app", | ||||
| 				"scheme": r.URL.Scheme, | ||||
| 				"method": r.Method, | ||||
| 				"path":   r.URL.Path, | ||||
| 				"host":   web.GetHost(r), | ||||
| 				"user":   user, | ||||
| 			}).Observe(float64(after)) | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	// Support /start and /sign_in for backwards compatibility | ||||
| 	mux.HandleFunc("/akprox/start", a.handleRedirect) | ||||
| 	mux.HandleFunc("/akprox/sign_in", a.handleRedirect) | ||||
| 	mux.HandleFunc("/akprox/callback", a.handleCallback) | ||||
| 	mux.HandleFunc("/akprox/sign_out", a.handleSignOut) | ||||
| 	switch *p.Mode { | ||||
| 	case api.PROXYMODE_PROXY: | ||||
| 		err = a.configureProxy() | ||||
| 	case api.PROXYMODE_FORWARD_SINGLE: | ||||
| 		fallthrough | ||||
| 	case api.PROXYMODE_FORWARD_DOMAIN: | ||||
| 		err = a.configureForward() | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		a.log.WithError(err).Warning("failed to configure mode") | ||||
| 	} | ||||
|  | ||||
| 	if kp := p.Certificate.Get(); kp != nil { | ||||
| 		err := cs.AddKeypair(*kp) | ||||
| 		if err != nil { | ||||
| 			a.log.WithError(err).Warning("Failed to initially fetch certificate") | ||||
| 		} | ||||
| 		a.Cert = cs.Get(*kp) | ||||
| 	} | ||||
|  | ||||
| 	if *p.SkipPathRegex != "" { | ||||
| 		a.UnauthenticatedRegex = make([]*regexp.Regexp, 0) | ||||
| 		for _, regex := range strings.Split(*p.SkipPathRegex, "\n") { | ||||
| 			re, err := regexp.Compile(regex) | ||||
| 			if err != nil { | ||||
| 				// TODO: maybe create event for this? | ||||
| 				a.log.WithError(err).Warning("failed to compile regex") | ||||
| 			} else { | ||||
| 				a.UnauthenticatedRegex = append(a.UnauthenticatedRegex, re) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| func (a *Application) IsAllowlisted(r *http.Request) bool { | ||||
| 	for _, u := range a.UnauthenticatedRegex { | ||||
| 		if u.MatchString(r.URL.Path) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (a *Application) Mode() api.ProxyMode { | ||||
| 	return *a.proxyConfig.Mode | ||||
| } | ||||
|  | ||||
| func (a *Application) ServeHTTP(rw http.ResponseWriter, r *http.Request) { | ||||
| 	a.mux.ServeHTTP(rw, r) | ||||
| } | ||||
|  | ||||
| func (a *Application) handleSignOut(rw http.ResponseWriter, r *http.Request) { | ||||
| 	// TODO: Token revocation | ||||
| 	s, err := a.sessions.Get(r, constants.SeesionName) | ||||
| 	if err != nil { | ||||
| 		http.Redirect(rw, r, a.proxyConfig.OidcConfiguration.EndSessionEndpoint, http.StatusFound) | ||||
| 		return | ||||
| 	} | ||||
| 	s.Options.MaxAge = -1 | ||||
| 	err = s.Save(r, rw) | ||||
| 	if err != nil { | ||||
| 		http.Redirect(rw, r, a.proxyConfig.OidcConfiguration.EndSessionEndpoint, http.StatusFound) | ||||
| 		return | ||||
| 	} | ||||
| 	http.Redirect(rw, r, a.proxyConfig.OidcConfiguration.EndSessionEndpoint, http.StatusFound) | ||||
| } | ||||
							
								
								
									
										16
									
								
								internal/outpost/proxyv2/application/claims.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								internal/outpost/proxyv2/application/claims.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| package application | ||||
|  | ||||
| type ProxyClaims struct { | ||||
| 	UserAttributes map[string]interface{} `json:"user_attributes"` | ||||
| } | ||||
|  | ||||
| type Claims struct { | ||||
| 	Sub               string      `json:"sub"` | ||||
| 	Exp               int         `json:"exp"` | ||||
| 	Email             string      `json:"email"` | ||||
| 	Verified          bool        `json:"email_verified"` | ||||
| 	Proxy             ProxyClaims `json:"ak_proxy"` | ||||
| 	Name              string      `json:"name"` | ||||
| 	PreferredUsername string      `json:"preferred_username"` | ||||
| 	Groups            []string    `json:"groups"` | ||||
| } | ||||
							
								
								
									
										29
									
								
								internal/outpost/proxyv2/application/error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								internal/outpost/proxyv2/application/error.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"html/template" | ||||
| 	"net/http" | ||||
|  | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| // NewProxyErrorHandler creates a ProxyErrorHandler using the template given. | ||||
| func NewProxyErrorHandler(errorTemplate *template.Template) func(http.ResponseWriter, *http.Request, error) { | ||||
| 	return func(rw http.ResponseWriter, req *http.Request, proxyErr error) { | ||||
| 		log.Errorf("Error proxying to upstream server: %v", proxyErr) | ||||
| 		rw.WriteHeader(http.StatusBadGateway) | ||||
| 		data := struct { | ||||
| 			Title       string | ||||
| 			Message     string | ||||
| 			ProxyPrefix string | ||||
| 		}{ | ||||
| 			Title:       "Bad Gateway", | ||||
| 			Message:     "Error proxying to upstream server", | ||||
| 			ProxyPrefix: "/akprox", | ||||
| 		} | ||||
| 		err := errorTemplate.Execute(rw, data) | ||||
| 		if err != nil { | ||||
| 			http.Error(rw, "Internal Server Error", http.StatusInternalServerError) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										53
									
								
								internal/outpost/proxyv2/application/mode_common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								internal/outpost/proxyv2/application/mode_common.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func (a *Application) addHeaders(r *http.Request, c *Claims) { | ||||
| 	// https://goauthentik.io/docs/providers/proxy/proxy | ||||
| 	r.Header.Set("X-Auth-Username", c.PreferredUsername) | ||||
| 	r.Header.Set("X-Auth-Groups", strings.Join(c.Groups, "|")) | ||||
| 	r.Header.Set("X-Forwarded-Email", c.Email) | ||||
| 	r.Header.Set("X-Forwarded-Preferred-Username", c.PreferredUsername) | ||||
| 	r.Header.Set("X-Forwarded-User", c.Sub) | ||||
|  | ||||
| 	userAttributes := c.Proxy.UserAttributes | ||||
| 	// Attempt to set basic auth based on user's attributes | ||||
| 	if *a.proxyConfig.BasicAuthEnabled { | ||||
| 		var ok bool | ||||
| 		var password string | ||||
| 		if password, ok = userAttributes[*a.proxyConfig.BasicAuthPasswordAttribute].(string); !ok { | ||||
| 			password = "" | ||||
| 		} | ||||
| 		// Check if we should use email or a custom attribute as username | ||||
| 		var username string | ||||
| 		if username, ok = userAttributes[*a.proxyConfig.BasicAuthUserAttribute].(string); !ok { | ||||
| 			username = c.Email | ||||
| 		} | ||||
| 		authVal := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) | ||||
| 		a.log.WithField("username", username).Trace("setting http basic auth") | ||||
| 		r.Header["Authorization"] = []string{fmt.Sprintf("Basic %s", authVal)} | ||||
| 	} | ||||
| 	// Check if user has additional headers set that we should sent | ||||
| 	if additionalHeaders, ok := userAttributes["additionalHeaders"].(map[string]interface{}); ok { | ||||
| 		a.log.WithField("headers", additionalHeaders).Trace("setting additional headers") | ||||
| 		if additionalHeaders == nil { | ||||
| 			return | ||||
| 		} | ||||
| 		for key, value := range additionalHeaders { | ||||
| 			r.Header.Set(key, toString(value)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func copyHeadersToResponse(rw http.ResponseWriter, r *http.Request) { | ||||
| 	for headerKey, headers := range r.Header { | ||||
| 		for _, value := range headers { | ||||
| 			rw.Header().Set(headerKey, value) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										77
									
								
								internal/outpost/proxyv2/application/mode_forward.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								internal/outpost/proxyv2/application/mode_forward.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
|  | ||||
| 	"goauthentik.io/api" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| ) | ||||
|  | ||||
| func (a *Application) configureForward() error { | ||||
| 	a.mux.HandleFunc("/akprox/auth", func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		if _, ok := r.URL.Query()["traefik"]; ok { | ||||
| 			a.forwardHandleTraefik(rw, r) | ||||
| 			return | ||||
| 		} | ||||
| 		a.forwardHandleNginx(rw, r) | ||||
| 	}) | ||||
| 	a.mux.HandleFunc("/akprox/auth/traefik", a.forwardHandleTraefik) | ||||
| 	a.mux.HandleFunc("/akprox/auth/nginx", a.forwardHandleNginx) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Request) { | ||||
| 	claims, err := a.getClaims(r) | ||||
| 	if claims != nil && err == nil { | ||||
| 		a.addHeaders(r, claims) | ||||
| 		copyHeadersToResponse(rw, r) | ||||
| 		return | ||||
| 	} else if claims == nil && a.IsAllowlisted(r) { | ||||
| 		a.log.Trace("path can be accessed without authentication") | ||||
| 		return | ||||
| 	} | ||||
| 	host := "" | ||||
| 	// Optional suffix, which is appended to the URL | ||||
| 	suffix := "" | ||||
| 	if *a.proxyConfig.Mode == api.PROXYMODE_FORWARD_SINGLE { | ||||
| 		host = web.GetHost(r) | ||||
| 	} else if *a.proxyConfig.Mode == api.PROXYMODE_FORWARD_DOMAIN { | ||||
| 		host = a.proxyConfig.ExternalHost | ||||
| 		// set the ?rd flag to the current URL we have, since we redirect | ||||
| 		// to a (possibly) different domain, but we want to be redirected back | ||||
| 		// to the application | ||||
| 		v := url.Values{ | ||||
| 			// see https://doc.traefik.io/traefik/middlewares/forwardauth/ | ||||
| 			// X-Forwarded-Uri is only the path, so we need to build the entire URL | ||||
| 			"rd": []string{fmt.Sprintf( | ||||
| 				"%s://%s%s", | ||||
| 				r.Header.Get("X-Forwarded-Proto"), | ||||
| 				r.Header.Get("X-Forwarded-Host"), | ||||
| 				r.Header.Get("X-Forwarded-Uri"), | ||||
| 			)}, | ||||
| 		} | ||||
| 		suffix = fmt.Sprintf("?%s", v.Encode()) | ||||
| 	} | ||||
| 	proto := r.Header.Get("X-Forwarded-Proto") | ||||
| 	if proto != "" { | ||||
| 		proto = proto + ":" | ||||
| 	} | ||||
| 	rdFinal := fmt.Sprintf("%s//%s%s%s", proto, host, "/akprox/start", suffix) | ||||
| 	a.log.WithField("url", rdFinal).Debug("Redirecting to login") | ||||
| 	http.Redirect(rw, r, rdFinal, http.StatusTemporaryRedirect) | ||||
| } | ||||
|  | ||||
| func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request) { | ||||
| 	claims, err := a.getClaims(r) | ||||
| 	if claims != nil && err == nil { | ||||
| 		a.addHeaders(r, claims) | ||||
| 		copyHeadersToResponse(rw, r) | ||||
| 		return | ||||
| 	} else if claims == nil && a.IsAllowlisted(r) { | ||||
| 		a.log.Trace("path can be accessed without authentication") | ||||
| 		return | ||||
| 	} | ||||
| 	http.Error(rw, "unauthorized request", http.StatusUnauthorized) | ||||
| } | ||||
							
								
								
									
										63
									
								
								internal/outpost/proxyv2/application/mode_proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								internal/outpost/proxyv2/application/mode_proxy.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"net/http/httputil" | ||||
| 	"net/url" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/metrics" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/templates" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| ) | ||||
|  | ||||
| func (a *Application) configureProxy() error { | ||||
| 	// Reverse proxy to the application server | ||||
| 	u, err := url.Parse(*a.proxyConfig.InternalHost) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	rp := &httputil.ReverseProxy{Director: a.proxyModifyRequest(u)} | ||||
| 	rp.ErrorHandler = NewProxyErrorHandler(templates.GetTemplates()) | ||||
| 	rp.ModifyResponse = a.proxyModifyResponse | ||||
| 	a.mux.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		claims, err := a.getClaims(r) | ||||
| 		if claims == nil && a.IsAllowlisted(r) { | ||||
| 			a.log.Trace("path can be accessed without authentication") | ||||
| 		} else if claims == nil && err != nil { | ||||
| 			a.redirectToStart(rw, r) | ||||
| 			return | ||||
| 		} else { | ||||
| 			a.addHeaders(r, claims) | ||||
| 		} | ||||
| 		before := time.Now() | ||||
| 		rp.ServeHTTP(rw, r) | ||||
| 		after := time.Since(before) | ||||
|  | ||||
| 		user := "" | ||||
| 		if claims != nil { | ||||
| 			user = claims.Email | ||||
| 		} | ||||
| 		metrics.UpstreamTiming.With(prometheus.Labels{ | ||||
| 			"upstream_host": u.String(), | ||||
| 			"scheme":        r.URL.Scheme, | ||||
| 			"method":        r.Method, | ||||
| 			"path":          r.URL.Path, | ||||
| 			"host":          web.GetHost(r), | ||||
| 			"user":          user, | ||||
| 		}).Observe(float64(after)) | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (a *Application) proxyModifyRequest(u *url.URL) func(req *http.Request) { | ||||
| 	return func(req *http.Request) { | ||||
| 		req.URL.Scheme = u.Scheme | ||||
| 		req.URL.Host = u.Host | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (a *Application) proxyModifyResponse(res *http.Response) error { | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										54
									
								
								internal/outpost/proxyv2/application/oauth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								internal/outpost/proxyv2/application/oauth.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/gorilla/securecookie" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/constants" | ||||
| ) | ||||
|  | ||||
| func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) { | ||||
| 	state := base64.RawStdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) | ||||
| 	s, _ := a.sessions.Get(r, constants.SeesionName) | ||||
| 	s.Values[constants.SessionOAuthState] = state | ||||
| 	err := s.Save(r, rw) | ||||
| 	if err != nil { | ||||
| 		a.log.WithError(err).Warning("failed to save session") | ||||
| 	} | ||||
| 	http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(state), http.StatusFound) | ||||
| } | ||||
|  | ||||
| func (a *Application) handleCallback(rw http.ResponseWriter, r *http.Request) { | ||||
| 	s, _ := a.sessions.Get(r, constants.SeesionName) | ||||
| 	state, ok := s.Values[constants.SessionOAuthState] | ||||
| 	if !ok { | ||||
| 		a.log.Warning("No state saved in session") | ||||
| 		http.Redirect(rw, r, a.proxyConfig.ExternalHost, http.StatusFound) | ||||
| 		return | ||||
| 	} | ||||
| 	claims, err := a.redeemCallback(r, state.(string)) | ||||
| 	if err != nil { | ||||
| 		a.log.WithError(err).Warning("failed to redeem code") | ||||
| 		rw.WriteHeader(400) | ||||
| 		// To prevent the user from just refreshing and cause more errors, delete | ||||
| 		// the state from the session | ||||
| 		delete(s.Values, constants.SessionOAuthState) | ||||
| 		err := s.Save(r, rw) | ||||
| 		if err != nil { | ||||
| 			a.log.WithError(err).Warning("failed to save session") | ||||
| 			rw.WriteHeader(400) | ||||
| 			return | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	s.Options.MaxAge = claims.Exp / 1000 | ||||
| 	s.Values[constants.SessionClaims] = &claims | ||||
| 	err = s.Save(r, rw) | ||||
| 	if err != nil { | ||||
| 		a.log.WithError(err).Warning("failed to save session") | ||||
| 		rw.WriteHeader(400) | ||||
| 		return | ||||
| 	} | ||||
| 	http.Redirect(rw, r, a.proxyConfig.ExternalHost, http.StatusFound) | ||||
| } | ||||
							
								
								
									
										49
									
								
								internal/outpost/proxyv2/application/oauth_callback.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								internal/outpost/proxyv2/application/oauth_callback.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"golang.org/x/oauth2" | ||||
| ) | ||||
|  | ||||
| func (a *Application) redeemCallback(r *http.Request, shouldState string) (*Claims, error) { | ||||
| 	state := r.URL.Query().Get("state") | ||||
| 	if state == "" || state != shouldState { | ||||
| 		return nil, fmt.Errorf("blank/invalid state") | ||||
| 	} | ||||
|  | ||||
| 	code := r.URL.Query().Get("code") | ||||
| 	if code == "" { | ||||
| 		return nil, fmt.Errorf("blank code") | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.WithValue(r.Context(), oauth2.HTTPClient, a.httpClient) | ||||
| 	// Verify state and errors. | ||||
| 	oauth2Token, err := a.oauthConfig.Exchange(ctx, code) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Extract the ID Token from OAuth2 token. | ||||
| 	rawIDToken, ok := oauth2Token.Extra("id_token").(string) | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("missing id_token") | ||||
| 	} | ||||
|  | ||||
| 	a.log.WithField("id_token", rawIDToken).Trace("id_token") | ||||
|  | ||||
| 	// Parse and verify ID Token payload. | ||||
| 	idToken, err := a.tokenVerifier.Verify(ctx, rawIDToken) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Extract custom claims | ||||
| 	var claims *Claims | ||||
| 	if err := idToken.Claims(&claims); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return claims, nil | ||||
| } | ||||
							
								
								
									
										28
									
								
								internal/outpost/proxyv2/application/session.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								internal/outpost/proxyv2/application/session.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/gorilla/sessions" | ||||
| 	"goauthentik.io/api" | ||||
| 	"goauthentik.io/internal/config" | ||||
| 	"gopkg.in/boj/redistore.v1" | ||||
| ) | ||||
|  | ||||
| func GetStore(p api.ProxyOutpostConfig) sessions.Store { | ||||
| 	var store sessions.Store | ||||
| 	if config.G.Redis.Host != "" { | ||||
| 		rs, err := redistore.NewRediStoreWithDB(10, "tcp", fmt.Sprintf("%s:%d", config.G.Redis.Host, config.G.Redis.Port), config.G.Redis.Password, strconv.Itoa(config.G.Redis.OutpostSessionDB)) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		rs.Options.Domain = *p.CookieDomain | ||||
| 		store = rs | ||||
| 	} else { | ||||
| 		cs := sessions.NewCookieStore([]byte(*p.CookieSecret)) | ||||
| 		cs.Options.Domain = *p.CookieDomain | ||||
| 		store = cs | ||||
| 	} | ||||
| 	return store | ||||
| } | ||||
							
								
								
									
										56
									
								
								internal/outpost/proxyv2/application/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								internal/outpost/proxyv2/application/utils.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/constants" | ||||
| ) | ||||
|  | ||||
| func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) { | ||||
| 	authUrl := fmt.Sprintf("%s/akprox/start", a.proxyConfig.ExternalHost) | ||||
| 	http.Redirect(rw, r, authUrl, http.StatusFound) | ||||
| } | ||||
|  | ||||
| // getClaims Get claims which are currently in session | ||||
| // Returns an error if the session can't be loaded or the claims can't be parsed/type-cast | ||||
| func (a *Application) getClaims(r *http.Request) (*Claims, error) { | ||||
| 	s, err := a.sessions.Get(r, constants.SeesionName) | ||||
| 	if err != nil { | ||||
| 		// err == user has no session/session is not valid, reject | ||||
| 		return nil, fmt.Errorf("invalid session") | ||||
| 	} | ||||
| 	claims, ok := s.Values[constants.SessionClaims] | ||||
| 	if claims == nil || !ok { | ||||
| 		// no claims saved, reject | ||||
| 		return nil, fmt.Errorf("invalid session") | ||||
| 	} | ||||
| 	c, ok := claims.(Claims) | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("invalid session") | ||||
| 	} | ||||
| 	return &c, nil | ||||
| } | ||||
|  | ||||
| func contains(s []string, e string) bool { | ||||
| 	for _, a := range s { | ||||
| 		if a == e { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // toString Generic to string function, currently supports actual strings and integers | ||||
| func toString(in interface{}) string { | ||||
| 	switch v := in.(type) { | ||||
| 	case string: | ||||
| 		return v | ||||
| 	case *string: | ||||
| 		return *v | ||||
| 	case int: | ||||
| 		return strconv.Itoa(v) | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
							
								
								
									
										6
									
								
								internal/outpost/proxyv2/constants/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								internal/outpost/proxyv2/constants/constants.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| package constants | ||||
|  | ||||
| const SeesionName = "authentik_proxy" | ||||
|  | ||||
| const SessionOAuthState = "oauth_state" | ||||
| const SessionClaims = "claims" | ||||
							
								
								
									
										48
									
								
								internal/outpost/proxyv2/handlers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								internal/outpost/proxyv2/handlers.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| package proxyv2 | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/metrics" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| 	staticWeb "goauthentik.io/web" | ||||
| ) | ||||
|  | ||||
| func (ps *ProxyServer) HandlePing(rw http.ResponseWriter, r *http.Request) { | ||||
| 	before := time.Now() | ||||
| 	rw.WriteHeader(204) | ||||
| 	after := time.Since(before) | ||||
| 	metrics.Requests.With(prometheus.Labels{ | ||||
| 		"type":   "ping", | ||||
| 		"method": r.Method, | ||||
| 		"path":   r.URL.Path, | ||||
| 		"host":   web.GetHost(r), | ||||
| 	}).Observe(float64(after)) | ||||
| } | ||||
|  | ||||
| func (ps *ProxyServer) HandleStatic(rw http.ResponseWriter, r *http.Request) { | ||||
| 	staticFs := http.FileServer(http.FS(staticWeb.StaticDist)) | ||||
| 	before := time.Now() | ||||
| 	http.StripPrefix("/akprox/static", staticFs).ServeHTTP(rw, r) | ||||
| 	after := time.Since(before) | ||||
| 	metrics.Requests.With(prometheus.Labels{ | ||||
| 		"type":   "static", | ||||
| 		"method": r.Method, | ||||
| 		"path":   r.URL.Path, | ||||
| 		"host":   web.GetHost(r), | ||||
| 	}).Observe(float64(after)) | ||||
| } | ||||
|  | ||||
| func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) { | ||||
| 	host := web.GetHost(r) | ||||
| 	a, ok := ps.apps[host] | ||||
| 	if !ok { | ||||
| 		ps.log.WithField("host", host).Warning("no app for hostname") | ||||
| 		rw.WriteHeader(400) | ||||
| 		return | ||||
| 	} | ||||
| 	ps.log.WithField("host", host).Trace("passing to application mux") | ||||
| 	a.ServeHTTP(rw, r) | ||||
| } | ||||
							
								
								
									
										31
									
								
								internal/outpost/proxyv2/hs256/hs256.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								internal/outpost/proxyv2/hs256/hs256.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| package hs256 | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/base64" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/golang-jwt/jwt" | ||||
| ) | ||||
|  | ||||
| type KeySet struct { | ||||
| 	m      jwt.SigningMethod | ||||
| 	secret string | ||||
| } | ||||
|  | ||||
| func NewKeySet(secret string) *KeySet { | ||||
| 	return &KeySet{ | ||||
| 		m:      jwt.GetSigningMethod("HS256"), | ||||
| 		secret: secret, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ks *KeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) { | ||||
| 	parts := strings.Split(jwt, ".") | ||||
| 	err := ks.m.Verify(strings.Join(parts[0:2], "."), parts[2], []byte(ks.secret)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	payload, err := base64.RawURLEncoding.DecodeString(parts[1]) | ||||
| 	return payload, err | ||||
| } | ||||
							
								
								
									
										30
									
								
								internal/outpost/proxyv2/metrics/metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								internal/outpost/proxyv2/metrics/metrics.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| package metrics | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 	"github.com/prometheus/client_golang/prometheus/promauto" | ||||
| 	"github.com/prometheus/client_golang/prometheus/promhttp" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{ | ||||
| 		Name: "authentik_outpost_proxy_requests", | ||||
| 		Help: "The total number of configured providers", | ||||
| 	}, []string{"scheme", "type", "method", "path", "host", "user"}) | ||||
| 	UpstreamTiming = promauto.NewHistogramVec(prometheus.HistogramOpts{ | ||||
| 		Name: "authentik_outpost_proxy_upstream_time", | ||||
| 		Help: "A summary of the duration we wait for the upstream reply", | ||||
| 	}, []string{"method", "scheme", "path", "host", "upstream_host", "user"}) | ||||
| ) | ||||
|  | ||||
| func RunServer() { | ||||
| 	m := mux.NewRouter() | ||||
| 	m.Path("/metrics").Handler(promhttp.Handler()) | ||||
| 	err := http.ListenAndServe("localhost:9300", m) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										202
									
								
								internal/outpost/proxyv2/proxyv2.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								internal/outpost/proxyv2/proxyv2.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,202 @@ | ||||
| package proxyv2 | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/pires/go-proxyproto" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/api" | ||||
| 	"goauthentik.io/internal/crypto" | ||||
| 	"goauthentik.io/internal/outpost/ak" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/application" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/metrics" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| ) | ||||
|  | ||||
| type ProxyServer struct { | ||||
| 	Listen     string | ||||
| 	PortOffset int | ||||
|  | ||||
| 	defaultCert tls.Certificate | ||||
| 	stop        chan struct{} // channel for waiting shutdown | ||||
|  | ||||
| 	cryptoStore *ak.CryptoStore | ||||
| 	apps        map[string]*application.Application | ||||
| 	log         *log.Entry | ||||
| 	mux         *mux.Router | ||||
| 	akAPI       *ak.APIController | ||||
| } | ||||
|  | ||||
| func NewProxyServer(ac *ak.APIController) *ProxyServer { | ||||
| 	l := log.WithField("logger", "authentik.outpost.proxyv2") | ||||
| 	defaultCert, err := crypto.GenerateSelfSignedCert() | ||||
| 	if err != nil { | ||||
| 		l.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	rootMux := mux.NewRouter() | ||||
| 	rootMux.Use(func(h http.Handler) http.Handler { | ||||
| 		return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 			h.ServeHTTP(rw, r) | ||||
| 			rw.Header().Set("Server", "authentik_proxy2") | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	globalMux := rootMux.NewRoute().Subrouter() | ||||
| 	globalMux.Use(web.NewLoggingHandler(l.WithField("logger", "authentik.outpost.proxyv2.http"), nil)) | ||||
| 	s := &ProxyServer{ | ||||
| 		Listen:     "0.0.0.0:%d", | ||||
| 		PortOffset: 0, | ||||
|  | ||||
| 		cryptoStore: ak.NewCryptoStore(ac.Client.CryptoApi), | ||||
| 		apps:        make(map[string]*application.Application), | ||||
| 		log:         l, | ||||
| 		mux:         rootMux, | ||||
| 		akAPI:       ac, | ||||
| 		defaultCert: defaultCert, | ||||
| 	} | ||||
| 	globalMux.Path("/akprox/ping").HandlerFunc(s.HandlePing) | ||||
| 	globalMux.PathPrefix("/akprox/static").HandlerFunc(s.HandleStatic) | ||||
| 	rootMux.PathPrefix("/").HandlerFunc(s.Handle) | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| func (ps *ProxyServer) HandleHost(host string, rw http.ResponseWriter, r *http.Request) bool { | ||||
| 	if app, ok := ps.apps[host]; ok { | ||||
| 		if app.Mode() == api.PROXYMODE_PROXY { | ||||
| 			ps.log.WithField("host", host).Trace("routing to proxy outpost") | ||||
| 			app.ServeHTTP(rw, r) | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (ps *ProxyServer) TimerFlowCacheExpiry() {} | ||||
|  | ||||
| func (ps *ProxyServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||||
| 	app, ok := ps.apps[info.ServerName] | ||||
| 	if !ok { | ||||
| 		ps.log.WithField("server-name", info.ServerName).Debug("app does not exist") | ||||
| 		return &ps.defaultCert, nil | ||||
| 	} | ||||
| 	if app.Cert == nil { | ||||
| 		ps.log.WithField("server-name", info.ServerName).Debug("app does not have a certificate") | ||||
| 		return &ps.defaultCert, nil | ||||
| 	} | ||||
| 	return app.Cert, nil | ||||
| } | ||||
|  | ||||
| // ServeHTTP constructs a net.Listener and starts handling HTTP requests | ||||
| func (ps *ProxyServer) ServeHTTP() { | ||||
| 	listenAddress := fmt.Sprintf(ps.Listen, 9000+ps.PortOffset) | ||||
| 	listener, err := net.Listen("tcp", listenAddress) | ||||
| 	if err != nil { | ||||
| 		ps.log.Fatalf("FATAL: listen (%s) failed - %s", listenAddress, err) | ||||
| 	} | ||||
| 	proxyListener := &proxyproto.Listener{Listener: listener} | ||||
| 	defer proxyListener.Close() | ||||
|  | ||||
| 	ps.log.Printf("listening on %s", listener.Addr()) | ||||
| 	ps.serve(proxyListener) | ||||
| 	ps.log.Printf("closing %s", listener.Addr()) | ||||
| } | ||||
|  | ||||
| // ServeHTTPS constructs a net.Listener and starts handling HTTPS requests | ||||
| func (ps *ProxyServer) ServeHTTPS() { | ||||
| 	listenAddress := fmt.Sprintf(ps.Listen, 9443+ps.PortOffset) | ||||
| 	config := &tls.Config{ | ||||
| 		MinVersion:     tls.VersionTLS12, | ||||
| 		MaxVersion:     tls.VersionTLS12, | ||||
| 		GetCertificate: ps.getCertificates, | ||||
| 	} | ||||
|  | ||||
| 	ln, err := net.Listen("tcp", listenAddress) | ||||
| 	if err != nil { | ||||
| 		ps.log.Fatalf("listen (%s) failed - %s", listenAddress, err) | ||||
| 	} | ||||
| 	ps.log.Printf("listening on %s", ln.Addr()) | ||||
|  | ||||
| 	proxyListener := &proxyproto.Listener{Listener: tcpKeepAliveListener{ln.(*net.TCPListener)}} | ||||
| 	defer proxyListener.Close() | ||||
|  | ||||
| 	tlsListener := tls.NewListener(proxyListener, config) | ||||
| 	ps.serve(tlsListener) | ||||
| 	ps.log.Printf("closing %s", tlsListener.Addr()) | ||||
| } | ||||
|  | ||||
| func (ps *ProxyServer) Start() error { | ||||
| 	wg := sync.WaitGroup{} | ||||
| 	wg.Add(3) | ||||
| 	go func() { | ||||
| 		defer wg.Done() | ||||
| 		ps.log.Debug("Starting HTTP Server...") | ||||
| 		ps.ServeHTTP() | ||||
| 	}() | ||||
| 	go func() { | ||||
| 		defer wg.Done() | ||||
| 		ps.log.Debug("Starting HTTPs Server...") | ||||
| 		ps.ServeHTTPS() | ||||
| 	}() | ||||
| 	go func() { | ||||
| 		defer wg.Done() | ||||
| 		ps.log.Debug("Starting Metrics Server...") | ||||
| 		metrics.RunServer() | ||||
| 	}() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (ps *ProxyServer) serve(listener net.Listener) { | ||||
| 	srv := &http.Server{Handler: ps.mux} | ||||
|  | ||||
| 	// See https://golang.org/pkg/net/http/#Server.Shutdown | ||||
| 	idleConnsClosed := make(chan struct{}) | ||||
| 	go func() { | ||||
| 		<-ps.stop // wait notification for stopping server | ||||
|  | ||||
| 		// We received an interrupt signal, shut down. | ||||
| 		if err := srv.Shutdown(context.Background()); err != nil { | ||||
| 			// Error from closing listeners, or context timeout: | ||||
| 			ps.log.Printf("HTTP server Shutdown: %v", err) | ||||
| 		} | ||||
| 		close(idleConnsClosed) | ||||
| 	}() | ||||
|  | ||||
| 	err := srv.Serve(listener) | ||||
| 	if err != nil && !errors.Is(err, http.ErrServerClosed) { | ||||
| 		ps.log.Errorf("ERROR: http.Serve() - %s", err) | ||||
| 	} | ||||
| 	<-idleConnsClosed | ||||
| } | ||||
|  | ||||
| // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted | ||||
| // connections. It's used by ListenAndServe and ListenAndServeTLS so | ||||
| // dead TCP connections (e.g. closing laptop mid-download) eventually | ||||
| // go away. | ||||
| type tcpKeepAliveListener struct { | ||||
| 	*net.TCPListener | ||||
| } | ||||
|  | ||||
| func (ln tcpKeepAliveListener) Accept() (net.Conn, error) { | ||||
| 	tc, err := ln.AcceptTCP() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	err = tc.SetKeepAlive(true) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Error setting Keep-Alive: %v", err) | ||||
| 	} | ||||
| 	err = tc.SetKeepAlivePeriod(3 * time.Minute) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Error setting Keep-Alive period: %v", err) | ||||
| 	} | ||||
| 	return tc, nil | ||||
| } | ||||
							
								
								
									
										33
									
								
								internal/outpost/proxyv2/refresh.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								internal/outpost/proxyv2/refresh.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| package proxyv2 | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"goauthentik.io/internal/constants" | ||||
| 	"goauthentik.io/internal/outpost/ak" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/application" | ||||
| ) | ||||
|  | ||||
| func (ps *ProxyServer) Refresh() error { | ||||
| 	providers, _, err := ps.akAPI.Client.OutpostsApi.OutpostsProxyList(context.Background()).Execute() | ||||
| 	if err != nil { | ||||
| 		ps.log.WithError(err).Error("Failed to fetch providers") | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	apps := make(map[string]*application.Application) | ||||
| 	for _, provider := range providers.Results { | ||||
| 		ua := fmt.Sprintf(" (provider=%s)", provider.Name) | ||||
| 		hc := &http.Client{ | ||||
| 			Transport: ak.NewUserAgentTransport(constants.OutpostUserAgent()+ua, ak.NewTracingTransport(context.TODO(), ak.GetTLSTransport())), | ||||
| 		} | ||||
| 		a := application.NewApplication(provider, hc, ps.cryptoStore) | ||||
| 		apps[a.Host] = a | ||||
| 	} | ||||
| 	ps.apps = apps | ||||
| 	ps.log.Debug("Swapped maps") | ||||
| 	return nil | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| {{define "error.html"}}<!DOCTYPE html> | ||||
| <!DOCTYPE html> | ||||
| 
 | ||||
| <html lang="en"> | ||||
|     <head> | ||||
| @ -62,4 +62,3 @@ | ||||
|         </div> | ||||
|     </body> | ||||
| </html> | ||||
| {{end}} | ||||
							
								
								
									
										18
									
								
								internal/outpost/proxyv2/templates/templates.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								internal/outpost/proxyv2/templates/templates.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| package templates | ||||
|  | ||||
| import ( | ||||
| 	_ "embed" | ||||
| 	"html/template" | ||||
| 	"log" | ||||
| ) | ||||
|  | ||||
| //go:embed error.html | ||||
| var ErrorTemplate string | ||||
|  | ||||
| func GetTemplates() *template.Template { | ||||
| 	t, err := template.New("foo").Parse(ErrorTemplate) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("failed parsing template %s", err) | ||||
| 	} | ||||
| 	return t | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package proxy | ||||
| package web | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| @ -9,7 +9,6 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| ) | ||||
| 
 | ||||
| // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status | ||||
| @ -19,7 +18,6 @@ type responseLogger struct { | ||||
| 	status   int | ||||
| 	size     int | ||||
| 	upstream string | ||||
| 	authInfo string | ||||
| } | ||||
| 
 | ||||
| // Header returns the ResponseWriter's Header | ||||
| @ -35,28 +33,12 @@ func (l *responseLogger) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err erro | ||||
| 	return nil, nil, errors.New("http.Hijacker is not available on writer") | ||||
| } | ||||
| 
 | ||||
| // ExtractGAPMetadata extracts and removes GAP headers from the ResponseWriter's | ||||
| // Header | ||||
| func (l *responseLogger) ExtractGAPMetadata() { | ||||
| 	upstream := l.w.Header().Get("GAP-Upstream-Address") | ||||
| 	if upstream != "" { | ||||
| 		l.upstream = upstream | ||||
| 		l.w.Header().Del("GAP-Upstream-Address") | ||||
| 	} | ||||
| 	authInfo := l.w.Header().Get("GAP-Auth") | ||||
| 	if authInfo != "" { | ||||
| 		l.authInfo = authInfo | ||||
| 		l.w.Header().Del("GAP-Auth") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Write writes the response using the ResponseWriter | ||||
| func (l *responseLogger) Write(b []byte) (int, error) { | ||||
| 	if l.status == 0 { | ||||
| 		// The status will be StatusOK if WriteHeader has not been called yet | ||||
| 		l.status = http.StatusOK | ||||
| 	} | ||||
| 	l.ExtractGAPMetadata() | ||||
| 	size, err := l.w.Write(b) | ||||
| 	l.size += size | ||||
| 	return size, err | ||||
| @ -64,7 +46,6 @@ func (l *responseLogger) Write(b []byte) (int, error) { | ||||
| 
 | ||||
| // WriteHeader writes the status code for the Response | ||||
| func (l *responseLogger) WriteHeader(s int) { | ||||
| 	l.ExtractGAPMetadata() | ||||
| 	l.w.WriteHeader(s) | ||||
| 	l.status = s | ||||
| } | ||||
| @ -88,15 +69,26 @@ func (l *responseLogger) Flush() { | ||||
| 
 | ||||
| // loggingHandler is the http.Handler implementation for LoggingHandler | ||||
| type loggingHandler struct { | ||||
| 	handler http.Handler | ||||
| 	logger  *log.Entry | ||||
| 	handler      http.Handler | ||||
| 	logger       *log.Entry | ||||
| 	afterHandler afterHandler | ||||
| } | ||||
| 
 | ||||
| // LoggingHandler provides an http.Handler which logs requests to the HTTP server | ||||
| func LoggingHandler(h http.Handler) http.Handler { | ||||
| 	return loggingHandler{ | ||||
| 		handler: h, | ||||
| 		logger:  log.WithField("logger", "authentik.outpost.proxy-http-server"), | ||||
| type afterHandler func(l *log.Entry, r *http.Request) *log.Entry | ||||
| 
 | ||||
| // NewLoggingHandler provides an http.Handler which logs requests to the HTTP server | ||||
| func NewLoggingHandler(logger *log.Entry, after afterHandler) func(h http.Handler) http.Handler { | ||||
| 	if after == nil { | ||||
| 		after = func(l *log.Entry, r *http.Request) *log.Entry { | ||||
| 			return l | ||||
| 		} | ||||
| 	} | ||||
| 	return func(h http.Handler) http.Handler { | ||||
| 		return loggingHandler{ | ||||
| 			handler:      h, | ||||
| 			logger:       logger, | ||||
| 			afterHandler: after, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -106,9 +98,9 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
| 	responseLogger := &responseLogger{w: w} | ||||
| 	h.handler.ServeHTTP(responseLogger, req) | ||||
| 	duration := float64(time.Since(t)) / float64(time.Millisecond) | ||||
| 	h.logger.WithFields(log.Fields{ | ||||
| 	h.afterHandler(h.logger.WithFields(log.Fields{ | ||||
| 		"host":              req.RemoteAddr, | ||||
| 		"vhost":             web.GetHost(req), | ||||
| 		"vhost":             GetHost(req), | ||||
| 		"request_protocol":  req.Proto, | ||||
| 		"runtime":           fmt.Sprintf("%0.3f", duration), | ||||
| 		"method":            req.Method, | ||||
| @ -116,6 +108,5 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
| 		"status":            responseLogger.Status(), | ||||
| 		"upstream":          responseLogger.upstream, | ||||
| 		"request_useragent": req.UserAgent(), | ||||
| 		"request_username":  responseLogger.authInfo, | ||||
| 	}).Info(url.RequestURI()) | ||||
| 	}), req).Info(url.RequestURI()) | ||||
| } | ||||
| @ -11,7 +11,7 @@ import ( | ||||
| 	"github.com/pires/go-proxyproto" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/internal/config" | ||||
| 	"goauthentik.io/internal/outpost/proxy" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2" | ||||
| ) | ||||
|  | ||||
| type WebServer struct { | ||||
| @ -22,7 +22,7 @@ type WebServer struct { | ||||
|  | ||||
| 	stop chan struct{} // channel for waiting shutdown | ||||
|  | ||||
| 	ProxyServer *proxy.Server | ||||
| 	ProxyServer *proxyv2.ProxyServer | ||||
|  | ||||
| 	m   *mux.Router | ||||
| 	lh  *mux.Router | ||||
|  | ||||
| @ -6,7 +6,6 @@ import ( | ||||
| 	"net/http/httputil" | ||||
| 	"net/url" | ||||
|  | ||||
| 	"goauthentik.io/api" | ||||
| 	"goauthentik.io/internal/utils/web" | ||||
| ) | ||||
|  | ||||
| @ -29,7 +28,7 @@ func (ws *WebServer) configureProxy() { | ||||
| 	rp.ModifyResponse = ws.proxyModifyResponse | ||||
| 	ws.m.PathPrefix("/akprox").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		if ws.ProxyServer != nil { | ||||
| 			ws.ProxyServer.Handler(rw, r) | ||||
| 			ws.ProxyServer.Handle(rw, r) | ||||
| 			return | ||||
| 		} | ||||
| 		ws.proxyErrorHandler(rw, r, fmt.Errorf("proxy not running")) | ||||
| @ -37,12 +36,8 @@ func (ws *WebServer) configureProxy() { | ||||
| 	ws.m.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		host := web.GetHost(r) | ||||
| 		if ws.ProxyServer != nil { | ||||
| 			if p, ok := ws.ProxyServer.Handlers[host]; ok { | ||||
| 				if *p.Mode == api.PROXYMODE_PROXY { | ||||
| 					ws.log.WithField("host", host).Trace("routing to proxy outpost") | ||||
| 					ws.ProxyServer.Handler(rw, r) | ||||
| 					return | ||||
| 				} | ||||
| 			if ws.ProxyServer.HandleHost(host, rw, r) { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		ws.log.WithField("host", host).Trace("routing to application server") | ||||
|  | ||||
| @ -31,6 +31,6 @@ ENV GIT_BUILD_HASH=$GIT_BUILD_HASH | ||||
|  | ||||
| COPY --from=builder /go/ldap / | ||||
|  | ||||
| HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:4180/akprox/ping" ] | ||||
| HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9000/akprox/ping" ] | ||||
|  | ||||
| ENTRYPOINT ["/ldap"] | ||||
|  | ||||
| @ -43,6 +43,6 @@ ENV GIT_BUILD_HASH=$GIT_BUILD_HASH | ||||
|  | ||||
| COPY --from=builder /go/proxy / | ||||
|  | ||||
| HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:4180/akprox/ping" ] | ||||
| HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9000/akprox/ping" ] | ||||
|  | ||||
| ENTRYPOINT ["/proxy"] | ||||
|  | ||||
							
								
								
									
										12
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								schema.yml
									
									
									
									
									
								
							| @ -25930,6 +25930,10 @@ components: | ||||
|             Exclusive with internal_host. | ||||
|         cookie_domain: | ||||
|           type: string | ||||
|         token_validity: | ||||
|           type: string | ||||
|           description: 'Tokens not valid on or after current time + this value (Format: | ||||
|             hours=1;minutes=2;seconds=3).' | ||||
|     PatchedReputationPolicyRequest: | ||||
|       type: object | ||||
|       description: Reputation Policy Serializer | ||||
| @ -27135,6 +27139,10 @@ components: | ||||
|           readOnly: true | ||||
|         cookie_domain: | ||||
|           type: string | ||||
|         token_validity: | ||||
|           type: string | ||||
|           description: 'Tokens not valid on or after current time + this value (Format: | ||||
|             hours=1;minutes=2;seconds=3).' | ||||
|       required: | ||||
|       - assigned_application_name | ||||
|       - assigned_application_slug | ||||
| @ -27200,6 +27208,10 @@ components: | ||||
|             Exclusive with internal_host. | ||||
|         cookie_domain: | ||||
|           type: string | ||||
|         token_validity: | ||||
|           type: string | ||||
|           description: 'Tokens not valid on or after current time + this value (Format: | ||||
|             hours=1;minutes=2;seconds=3).' | ||||
|       required: | ||||
|       - authorization_flow | ||||
|       - external_host | ||||
|  | ||||
| @ -15,7 +15,14 @@ from authentik.flows.models import Flow | ||||
| from authentik.outposts.managed import MANAGED_OUTPOST | ||||
| from authentik.outposts.models import Outpost, OutpostType | ||||
| from authentik.providers.ldap.models import LDAPProvider | ||||
| from tests.e2e.utils import USER, SeleniumTestCase, apply_migration, object_manager, retry | ||||
| from tests.e2e.utils import ( | ||||
|     USER, | ||||
|     SeleniumTestCase, | ||||
|     apply_migration, | ||||
|     get_docker_tag, | ||||
|     object_manager, | ||||
|     retry, | ||||
| ) | ||||
|  | ||||
|  | ||||
| @skipUnless(platform.startswith("linux"), "requires local docker") | ||||
| @ -33,7 +40,7 @@ class TestProviderLDAP(SeleniumTestCase): | ||||
|         """Start ldap container based on outpost created""" | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run( | ||||
|             image="beryju.org/authentik/outpost-ldap:gh-master", | ||||
|             image=f"beryju.org/authentik/outpost-ldap:{get_docker_tag()}", | ||||
|             detach=True, | ||||
|             network_mode="host", | ||||
|             auto_remove=True, | ||||
|  | ||||
| @ -16,7 +16,14 @@ from authentik.flows.models import Flow | ||||
| from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostConfig, OutpostType | ||||
| from authentik.outposts.tasks import outpost_local_connection | ||||
| from authentik.providers.proxy.models import ProxyProvider | ||||
| from tests.e2e.utils import USER, SeleniumTestCase, apply_migration, object_manager, retry | ||||
| from tests.e2e.utils import ( | ||||
|     USER, | ||||
|     SeleniumTestCase, | ||||
|     apply_migration, | ||||
|     get_docker_tag, | ||||
|     object_manager, | ||||
|     retry, | ||||
| ) | ||||
|  | ||||
|  | ||||
| @skipUnless(platform.startswith("linux"), "requires local docker") | ||||
| @ -42,7 +49,7 @@ class TestProviderProxy(SeleniumTestCase): | ||||
|         """Start proxy container based on outpost created""" | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run( | ||||
|             image="beryju.org/authentik/outpost-proxy:gh-master", | ||||
|             image=f"beryju.org/authentik/outpost-proxy:{get_docker_tag()}", | ||||
|             detach=True, | ||||
|             network_mode="host", | ||||
|             auto_remove=True, | ||||
| @ -73,7 +80,7 @@ class TestProviderProxy(SeleniumTestCase): | ||||
|                 slug="default-provider-authorization-implicit-consent" | ||||
|             ), | ||||
|             internal_host="http://localhost", | ||||
|             external_host="http://localhost:4180", | ||||
|             external_host="http://localhost:9000", | ||||
|         ) | ||||
|         # Ensure OAuth2 Params are set | ||||
|         proxy.set_oauth_defaults() | ||||
| @ -100,7 +107,7 @@ class TestProviderProxy(SeleniumTestCase): | ||||
|             healthcheck_retries += 1 | ||||
|             sleep(0.5) | ||||
|  | ||||
|         self.driver.get("http://localhost:4180") | ||||
|         self.driver.get("http://localhost:9000") | ||||
|         self.login() | ||||
|         sleep(1) | ||||
|  | ||||
| @ -108,7 +115,7 @@ class TestProviderProxy(SeleniumTestCase): | ||||
|         self.assertIn("X-Forwarded-Preferred-Username: akadmin", full_body_text) | ||||
|         self.assertIn("X-Foo: bar", full_body_text) | ||||
|  | ||||
|         self.driver.get("http://localhost:4180/akprox/sign_out") | ||||
|         self.driver.get("http://localhost:9000/akprox/sign_out") | ||||
|         sleep(2) | ||||
|         full_body_text = self.driver.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text | ||||
|         self.assertIn("You've logged out of proxy.", full_body_text) | ||||
| @ -134,7 +141,7 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase): | ||||
|                 slug="default-provider-authorization-implicit-consent" | ||||
|             ), | ||||
|             internal_host="http://localhost", | ||||
|             external_host="http://localhost:4180", | ||||
|             external_host="http://localhost:9000", | ||||
|         ) | ||||
|         # Ensure OAuth2 Params are set | ||||
|         proxy.set_oauth_defaults() | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| """authentik e2e testing utilities""" | ||||
| import json | ||||
| import os | ||||
| from functools import lru_cache, wraps | ||||
| from os import environ, makedirs | ||||
| from time import sleep, time | ||||
| @ -36,6 +37,17 @@ def USER() -> User:  # noqa | ||||
|     return User.objects.get(username="akadmin") | ||||
|  | ||||
|  | ||||
| def get_docker_tag() -> str: | ||||
|     """Get docker-tag based off of CI variables""" | ||||
|     env_pr_branch = "GITHUB_HEAD_REF" | ||||
|     default_branch = "GITHUB_REF" | ||||
|     branch_name = os.environ.get(default_branch, "master") | ||||
|     if os.environ.get(env_pr_branch, "") != "": | ||||
|         branch_name = os.environ[env_pr_branch] | ||||
|     branch_name = branch_name.replace("refs/heads/", "").replace("/", "-") | ||||
|     return f"gh-{branch_name}" | ||||
|  | ||||
|  | ||||
| class SeleniumTestCase(StaticLiveServerTestCase): | ||||
|     """StaticLiveServerTestCase which automatically creates a Webdriver instance""" | ||||
|  | ||||
|  | ||||
| @ -16,6 +16,7 @@ from authentik.outposts.controllers.docker import DockerController | ||||
| from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType | ||||
| from authentik.outposts.tasks import outpost_local_connection | ||||
| from authentik.providers.proxy.models import ProxyProvider | ||||
| from tests.e2e.utils import get_docker_tag | ||||
|  | ||||
|  | ||||
| class OutpostDockerTests(TestCase): | ||||
| @ -107,5 +108,5 @@ class OutpostDockerTests(TestCase): | ||||
|         self.assertEqual(compose["version"], "3.5") | ||||
|         self.assertEqual( | ||||
|             compose["services"]["authentik_proxy"]["image"], | ||||
|             "beryju.org/authentik/outpost-proxy:gh-master", | ||||
|             f"beryju.org/authentik/outpost-proxy:{get_docker_tag()}", | ||||
|         ) | ||||
|  | ||||
| @ -16,6 +16,7 @@ from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostT | ||||
| from authentik.outposts.tasks import outpost_local_connection | ||||
| from authentik.providers.proxy.controllers.docker import DockerController | ||||
| from authentik.providers.proxy.models import ProxyProvider | ||||
| from tests.e2e.utils import get_docker_tag | ||||
|  | ||||
|  | ||||
| class TestProxyDocker(TestCase): | ||||
| @ -107,5 +108,5 @@ class TestProxyDocker(TestCase): | ||||
|         self.assertEqual(compose["version"], "3.5") | ||||
|         self.assertEqual( | ||||
|             compose["services"]["authentik_proxy"]["image"], | ||||
|             "beryju.org/authentik/outpost-proxy:gh-master", | ||||
|             f"beryju.org/authentik/outpost-proxy:{get_docker_tag()}", | ||||
|         ) | ||||
|  | ||||
| @ -15,6 +15,7 @@ msgstr "" | ||||
|  | ||||
| #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | ||||
| #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | ||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||
| #: src/pages/providers/saml/SAMLProviderForm.ts | ||||
| #: src/pages/stages/user_login/UserLoginStageForm.ts | ||||
| msgid "(Format: hours=-1;minutes=-2;seconds=-3)." | ||||
| @ -794,6 +795,10 @@ msgstr "Configure how long access codes are valid for." | ||||
| msgid "Configure how long refresh tokens and their id_tokens are valid for." | ||||
| msgstr "Configure how long refresh tokens and their id_tokens are valid for." | ||||
|  | ||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||
| msgid "Configure how long tokens are valid for." | ||||
| msgstr "Configure how long tokens are valid for." | ||||
|  | ||||
| #: src/pages/providers/saml/SAMLProviderForm.ts | ||||
| msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." | ||||
| msgstr "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." | ||||
| @ -2053,6 +2058,10 @@ msgstr "If this is selected, the token will expire. Upon expiration, the token w | ||||
| msgid "If your authentik Instance is using a self-signed certificate, set this value." | ||||
| msgstr "If your authentik Instance is using a self-signed certificate, set this value." | ||||
|  | ||||
| #: src/pages/outposts/OutpostDeploymentModal.ts | ||||
| msgid "If your authentik_host setting does not match the URL you want to login with, add this setting." | ||||
| msgstr "If your authentik_host setting does not match the URL you want to login with, add this setting." | ||||
|  | ||||
| #: src/pages/users/UserListPage.ts | ||||
| msgid "Impersonate" | ||||
| msgstr "Impersonate" | ||||
| @ -4472,6 +4481,7 @@ msgid "Token expiry" | ||||
| msgstr "Token expiry" | ||||
|  | ||||
| #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | ||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||
| msgid "Token validity" | ||||
| msgstr "Token validity" | ||||
|  | ||||
|  | ||||
| @ -15,6 +15,7 @@ msgstr "" | ||||
|  | ||||
| #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | ||||
| #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | ||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||
| #: src/pages/providers/saml/SAMLProviderForm.ts | ||||
| #: src/pages/stages/user_login/UserLoginStageForm.ts | ||||
| msgid "(Format: hours=-1;minutes=-2;seconds=-3)." | ||||
| @ -788,6 +789,10 @@ msgstr "" | ||||
| msgid "Configure how long refresh tokens and their id_tokens are valid for." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||
| msgid "Configure how long tokens are valid for." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/providers/saml/SAMLProviderForm.ts | ||||
| msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." | ||||
| msgstr "" | ||||
| @ -2045,6 +2050,10 @@ msgstr "" | ||||
| msgid "If your authentik Instance is using a self-signed certificate, set this value." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/outposts/OutpostDeploymentModal.ts | ||||
| msgid "If your authentik_host setting does not match the URL you want to login with, add this setting." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/users/UserListPage.ts | ||||
| msgid "Impersonate" | ||||
| msgstr "" | ||||
| @ -4457,6 +4466,7 @@ msgid "Token expiry" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | ||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||
| msgid "Token validity" | ||||
| msgstr "" | ||||
|  | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Outpost } from "@goauthentik/api"; | ||||
| import { Outpost, OutpostTypeEnum } from "@goauthentik/api"; | ||||
| import { customElement, html, property, TemplateResult } from "lit-element"; | ||||
| import { t } from "@lingui/macro"; | ||||
| import { ifDefined } from "lit-html/directives/if-defined"; | ||||
| @ -53,6 +53,26 @@ export class OutpostDeploymentModal extends ModalButton { | ||||
|                         </label> | ||||
|                         <input class="pf-c-form-control" readonly type="text" value="true" /> | ||||
|                     </div> | ||||
|                     ${this.outpost?.type == OutpostTypeEnum.Proxy | ||||
|                         ? html` | ||||
|                               <h3> | ||||
|                                   ${t`If your authentik_host setting does not match the URL you want to login with, add this setting.`} | ||||
|                               </h3> | ||||
|                               <div class="pf-c-form__group"> | ||||
|                                   <label class="pf-c-form__label" for="help-text-simple-form-name"> | ||||
|                                       <span class="pf-c-form__label-text" | ||||
|                                           >AUTHENTIK_HOST_BROWSER</span | ||||
|                                       > | ||||
|                                   </label> | ||||
|                                   <input | ||||
|                                       class="pf-c-form-control" | ||||
|                                       readonly | ||||
|                                       type="text" | ||||
|                                       value="${document.location.origin}" | ||||
|                                   /> | ||||
|                               </div> | ||||
|                           ` | ||||
|                         : html``} | ||||
|                 </form> | ||||
|             </div> | ||||
|             <footer class="pf-c-modal-box__footer pf-m-align-left"> | ||||
|  | ||||
| @ -302,6 +302,17 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> { | ||||
|                 </div> | ||||
|                 <div class="pf-c-card__footer">${this.renderSettings()}</div> | ||||
|             </div> | ||||
|             <ak-form-element-horizontal label=${t`Token validity`} name="tokenValidity"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${first(this.instance?.tokenValidity, "hours=24")}" | ||||
|                     class="pf-c-form-control" | ||||
|                 /> | ||||
|                 <p class="pf-c-form__helper-text">${t`Configure how long tokens are valid for.`}</p> | ||||
|                 <p class="pf-c-form__helper-text"> | ||||
|                     ${t`(Format: hours=-1;minutes=-2;seconds=-3).`} | ||||
|                 </p> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-form-group> | ||||
|                 <span slot="header"> ${t`Advanced protocol settings`} </span> | ||||
|  | ||||
| @ -13,12 +13,15 @@ services: | ||||
|   authentik_proxy: | ||||
|     image: ghcr.io/goauthentik/proxy:2021.8.4 | ||||
|     ports: | ||||
|       - 4180:4180 | ||||
|       - 4443:4443 | ||||
|       - 9000:9000 | ||||
|       - 9443:9443 | ||||
|     environment: | ||||
|       AUTHENTIK_HOST: https://your-authentik.tld | ||||
|       AUTHENTIK_INSECURE: "false" | ||||
|       AUTHENTIK_TOKEN: token-generated-by-authentik | ||||
|       # Starting with 2021.10, you can optionally set this too | ||||
|       # when authentik_host for internal communication doesn't match the public URL | ||||
|       # AUTHENTIK_HOST_BROWSER: https://external-domain.tld | ||||
|   # Or, for the LDAP Outpost | ||||
|   authentik_proxy: | ||||
|     image: ghcr.io/goauthentik/ldap:2021.8.4 | ||||
|  | ||||
| @ -34,11 +34,11 @@ metadata: | ||||
| spec: | ||||
|   ports: | ||||
|     - name: http | ||||
|       port: 4180 | ||||
|       port: 9000 | ||||
|       protocol: TCP | ||||
|       targetPort: http | ||||
|     - name: https | ||||
|       port: 4443 | ||||
|       port: 9443s | ||||
|       protocol: TCP | ||||
|       targetPort: https | ||||
|   type: ClusterIP | ||||
| @ -91,10 +91,10 @@ spec: | ||||
|         image: ghcr.io/goauthentik/proxy:2021.8.4 | ||||
|         name: proxy | ||||
|         ports: | ||||
|           - containerPort: 4180 | ||||
|           - containerPort: 9000 | ||||
|             name: http | ||||
|             protocol: TCP | ||||
|           - containerPort: 4443 | ||||
|           - containerPort: 9443 | ||||
|             name: https | ||||
|             protocol: TCP | ||||
| --- | ||||
|  | ||||
| @ -81,7 +81,7 @@ server { | ||||
|  | ||||
|     # all requests to /akprox must be accessible without authentication | ||||
|     location /akprox { | ||||
|         proxy_pass          http://*ip or hostname of the authentik OUTPOST*:4180; | ||||
|         proxy_pass          http://*ip or hostname of the authentik OUTPOST*:9000; | ||||
|         # ensure the host of this vserver matches your external URL you've configured | ||||
|         # in authentik | ||||
|         proxy_set_header    Host $host; | ||||
| @ -115,7 +115,7 @@ spec: | ||||
|       paths: | ||||
|       - backend: | ||||
|           serviceName: authentik-outpost-example-outpost | ||||
|           servicePort: 4180 | ||||
|           servicePort: 9000 | ||||
|         path: /akprox | ||||
| ``` | ||||
|  | ||||
| @ -151,7 +151,7 @@ http: | ||||
|   middlewares: | ||||
|     authentik: | ||||
|       forwardAuth: | ||||
|         address: http://authentik-outpost-example-outpost:4180/akprox/auth?traefik | ||||
|         address: http://authentik-outpost-example-outpost:9000/akprox/auth?traefik | ||||
|         trustForwardHeader: true | ||||
|         authResponseHeaders: | ||||
|           - Set-Cookie | ||||
| @ -170,7 +170,7 @@ http: | ||||
|     default-router-auth | ||||
|       match: "Host(`*external host that you configured in authentik*`) && PathPrefix(`/akprox/`)" | ||||
|       priority: 15 | ||||
|       services: http://*ip of your outpost*:4180/akprox | ||||
|       services: http://*ip of your outpost*:9000/akprox | ||||
| ``` | ||||
|   </TabItem> | ||||
|   <TabItem value="docker-compose"> | ||||
| @ -209,19 +209,22 @@ services: | ||||
|   authentik_proxy: | ||||
|     image: ghcr.io/goauthentik/proxy:2021.5.1 | ||||
|     ports: | ||||
|       - 4180:4180 | ||||
|       - 4443:4443 | ||||
|       - 9000:9000 | ||||
|       - 9443:9443 | ||||
|     environment: | ||||
|       AUTHENTIK_HOST: https://your-authentik.tld | ||||
|       AUTHENTIK_INSECURE: "false" | ||||
|       AUTHENTIK_TOKEN: token-generated-by-authentik | ||||
|       # Starting with 2021.10, you can optionally set this too | ||||
|       # when authentik_host for internal communication doesn't match the public URL | ||||
|       # AUTHENTIK_HOST_BROWSER: https://external-domain.tld | ||||
|     labels: | ||||
|       traefik.enable: true | ||||
|       traefik.port: 4180 | ||||
|       traefik.port: 9000 | ||||
|       traefik.http.routers.authentik.rule: Host(`*external host that you configured in authentik*`) && PathPrefix(`/akprox/`) | ||||
|       traefik.http.routers.authentik.entrypoints: https | ||||
|       traefik.http.routers.authentik.tls: true | ||||
|       traefik.http.middlewares.authentik.forwardauth.address: http://authentik_proxy:4180/akprox/auth?traefik | ||||
|       traefik.http.middlewares.authentik.forwardauth.address: http://authentik_proxy:9000/akprox/auth?traefik | ||||
|       traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true | ||||
|       traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: Set-Cookie,X-Auth-Username,X-Auth-Groups,X-Forwarded-Email,X-Forwarded-Preferred-Username,X-Forwarded-User | ||||
|     restart: unless-stopped | ||||
| @ -248,7 +251,7 @@ metadata: | ||||
|   name: authentik | ||||
| spec: | ||||
|   forwardAuth: | ||||
|     address: http://authentik-outpost-example-outpost:4180/akprox/auth?traefik | ||||
|     address: http://authentik-outpost-example-outpost:9000/akprox/auth?traefik | ||||
|     trustForwardHeader: true | ||||
|     authResponseHeaders: | ||||
|       - Set-Cookie | ||||
| @ -284,7 +287,7 @@ spec: | ||||
|       services: | ||||
|         - kind: Service | ||||
|           name: authentik-outpost-example-outpost | ||||
|           port: 4180 | ||||
|           port: 9000 | ||||
| ``` | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
|  | ||||
| @ -18,7 +18,7 @@ If you enable *Set HTTP-Basic Authentication* option, the HTTP Authorization hea | ||||
|  | ||||
| # HTTPS | ||||
|  | ||||
| The outpost listens on both 4180 for HTTP and 4443 for HTTPS. | ||||
| The outpost listens on both 9000 for HTTP and 9443 for HTTPS. | ||||
|  | ||||
| :::info | ||||
| If your upstream host is HTTPS, and you're not using forward auth, you need to access the outpost over HTTPS too. | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L