envoyproxy / envoy

Cloud-native high-performance edge/middle/service proxy
https://www.envoyproxy.io
Apache License 2.0
24.74k stars 4.75k forks source link

Oauth implementation #13637

Closed juanvasquezreyes closed 3 years ago

juanvasquezreyes commented 3 years ago

Hello Envoy team

I'm trying to implement oauth, using envoy 1.16.0 I read the documentation provided at the following link https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/oauth2_filter, I dont think the documentation is correct, since I tried to use the sample describe and its not working

this is the error I'm facing, can you please update the documentation and provide a good example?

Unable to parse JSON as proto (INVALID_ARGUMENT:(http_filters[0].typed_config): invalid value Invalid type URL, unknown type: envoy.extensions.filters.http.oauth2.v3alpha.OAuth2 for type Any): {"clusters":[{"lb_policy":"ROUND_ROBIN","load_assignment":{"endpoints":[{"lb_endpoints":[{"endpoint":{"address":{"socket_address":{"address":"auth.example.com","port_value":443}}}}]}],"cluster_name":"auth"},"name":"auth","tls_context":{"sni":"auth.example.com"},"type":"LOGICAL_DNS","connect_timeout":"5s"}],"static_resources":{"listeners":[{"address":{"socket_address":{"address":"0.0.0.0","port_value":443}},"name":"oauth","filter_chains":[{"filters":[{"name":"envoy.filters.network.http_connection_manager","typed_config":{"http_filters":[{"typed_config":{"config":{"redirect_uri":"%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback","signout_path":{"path":{"exact":"/signout"}},"credentials":{"client_id":"foo"},"token_endpoint":{"uri":"oauth.com/token","cluster":"oauth","timeout":"3s"},"redirect_path_matcher":{"path":{"exact":"/callback"}},"authorization_endpoint":"https://oauth.com/oauth/authorize/"},"@type":"type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2"},"name":"envoy.filters.http.oauth2"},{"name":"envoy.router"}],"@type":"type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"}}]}]}]}}

this is my configuration file

static_resources:
  listeners:
  - name: oauth
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 443
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          http_filters:
          - name: envoy.filters.http.oauth2
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2
              config:
                token_endpoint:
                  cluster: oauth
                  uri: oauth.com/token
                  timeout: 3s
                authorization_endpoint: https://oauth.com/oauth/authorize/
                redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback"
                redirect_path_matcher:
                  path:
                    exact: /callback
                signout_path:
                  path:
                    exact: /signout
                credentials:
                  client_id: foo
                  token_secret:
                    name: token
                    sds_config:
                      path: "/etc/envoy/token-secret.yaml"
          - name: envoy.router

clusters:
- name: auth
  connect_timeout: 5s
  type: LOGICAL_DNS
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: auth
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: auth.example.com
              port_value: 443
  tls_context:
    sni: auth.example.com

also is there any special format for the token file? or how can we pass the token secret?

snowp commented 3 years ago

The error indicates that the OAuth2 filter isn't compiled into the binary. Are you building a custom set of extensions, implicitly disabling the OAuth filter?

juanvasquezreyes commented 3 years ago

The error indicates that the OAuth2 filter isn't compiled into the binary. Are you building a custom set of extensions, implicitly disabling the OAuth filter?

I'm using the latest docker version, does this version doesnt come with Oauth?

snowp commented 3 years ago

It does. I get a different error than you when I run your config against envoyproxy/envoy-alpine:v1.16.0, can you verify that the binary you're using is actually 1.16?

juanvasquezreyes commented 3 years ago

It does. I get a different error than you when I run your config against envoyproxy/envoy-alpine:v1.16.0, can you verify that the binary you're using is actually 1.16?

now I got this error, not sure what it means, is this the error you are getting too?

[2020-10-20 15:43:17.357][8][info][main] [source/server/server.cc:731] exiting
Proto constraint validation failed (HttpConnectionManagerValidationError.StatPrefix: ["value length must be at least " '\x01' " runes"]): http_filters {
  name: "envoy.filters.http.oauth2"
  typed_config {
    [type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2] {
      config {
        token_endpoint {
          uri: "oauth.com/token"
          cluster: "oauth"
          timeout {
            seconds: 3
          }
        }
        authorization_endpoint: "https://oauth.com/oauth/authorize/"
        credentials {
          client_id: "foo"
          token_secret {
            name: "token"
            sds_config {
              path: "/etc/envoy/token-secret.yaml"
            }
          }
          hmac_secret {
            name: "hmac"
            sds_config {
              path: "/etc/envoy/hmac.yaml"
            }
          }
        }
        redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback"
        redirect_path_matcher {
          path {
            exact: "/callback"
          }
        }
        signout_path {
          path {
            exact: "/signout"
          }
        }
      }
    }
  }
}
http_filters {
  name: "envoy.router"
}
snowp commented 3 years ago

Ok yeah it seems like the example is bad, it's missing the route config for the HCM config and the stat prefix.

cc @dio in case you have time to come up with a fix

juanvasquezreyes commented 3 years ago

also an example of the content of token secret file and hmac

Thank you

snowp commented 3 years ago

cc @rgs1 as well

juanvasquezreyes commented 3 years ago

Another question @rgs1 ,@dio, @snowp I'm planning to have envoy running in a server that will route my request to another app www.myapp.com, this app doesnt have any authorization defined, planning to use envoy to do the authorization and send it to my app

based on this, what should be in the redirect_uri key? myapp.com or my envoy instance /callback and with the call back a route config to my app.com?

also, how can I set the oidc token in a cookie and redirect it to myapp.com?

williamsfu99 commented 3 years ago

@juanvasquezreyes Your redirect_uri key should be the same uri used in the initial request before the redirect to the authn server occurs. The provided example %REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback is a great start, assuming you're okay with the /callback path being the filter's path to match on to determine whether or not it is handling a redirected request.

When the authn server validates the client and returns an authorization token back to the OAuth filter, no matter what format that token is, if forward_bearer_token is set to true the filter will send over a cookie named BearerToken to the upstream. Hopefully this helps.

andreyprezotto commented 3 years ago

@snowp could you figure out what is missing in the config below. I'm running into a similar issue here

error: Protobuf message (type envoy.config.bootstrap.v3.Bootstrap reason INVALID_ARGUMENT:clusters: Cannot find field.) has unknown fields

static_resources:
  listeners:
  - name: main
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8081
    filter_chains:
      - filters:
        - name: envoy.filters.network.http_connection_manager
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
            http_filters:
              - name: envoy.filters.http.oauth2
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2
                config:
                  token_endpoint:
                    cluster: oauth
                    uri: oauth.com/token
                    timeout: 3s
                  authorization_endpoint: https://okta.com/oauth2/id/v1/authorize
                  redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/oauth2/callback"
                  redirect_path_matcher:
                    path:
                      exact: /oauth2/callback
                  signout_path:
                    path:
                      exact: /signout
                  credentials:
                    client_id: id_hash
                    token_secret:
                      name: token
                      sds_config:
                        path: "/etc/envoy/token-secret.yaml"
                    hmac_secret:
                      name: hmac
                      sds_config:
                        path: "/etc/envoy/hmac.yaml"
              - name: envoy.filters.http.router
clusters:
- name: oauth
  connect_timeout: 3s
  type: LOGICAL_DNS
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: oauth
    endpoints:
      - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: test.okta.com
                  port_value: 443
  tls_context:
    sni: test.okta.com

Version: 1.17 thank you!

juanvasquezreyes commented 3 years ago

@snowp could you figure out what is missing in the config below. I'm running into a similar issue here

error: Protobuf message (type envoy.config.bootstrap.v3.Bootstrap reason INVALID_ARGUMENT:clusters: Cannot find field.) has unknown fields

static_resources:
  listeners:
  - name: main
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8081
    filter_chains:
      - filters:
        - name: envoy.filters.network.http_connection_manager
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
            http_filters:
              - name: envoy.filters.http.oauth2
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2
                config:
                  token_endpoint:
                    cluster: oauth
                    uri: oauth.com/token
                    timeout: 3s
                  authorization_endpoint: https://okta.com/oauth2/id/v1/authorize
                  redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/oauth2/callback"
                  redirect_path_matcher:
                    path:
                      exact: /oauth2/callback
                  signout_path:
                    path:
                      exact: /signout
                  credentials:
                    client_id: id_hash
                    token_secret:
                      name: token
                      sds_config:
                        path: "/etc/envoy/token-secret.yaml"
                    hmac_secret:
                      name: hmac
                      sds_config:
                        path: "/etc/envoy/hmac.yaml"
              - name: envoy.filters.http.router
clusters:
- name: oauth
  connect_timeout: 3s
  type: LOGICAL_DNS
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: oauth
    endpoints:
      - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: test.okta.com
                  port_value: 443
  tls_context:
    sni: test.okta.com

Version: 1.17 thank you!

Not sure if that si the way that is supposed to work, but I was able to fix those issues

      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          tracing: {}
          codec_type: "AUTO"
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
              - name: local_service
                domains:
                  - '*'
                routes:
                  - match:
                     prefix: /callback
                    route: mycluster

what I still dont understand is the /callback, I'm getting an issue "Oauth flow failed.", /callback

Also not sure why hmac is required, I'm trying to implement it like this link https://toolsqa.com/wp-content/uploads/sites/1/nggallery/postman/Get_Access_Token_Values.png

Maybe a full example of this implementation will be appreciated

williamsfu99 commented 3 years ago

@juanvasquezreyes I will try to improve the documentation for this filter this weekend.

The conventional OAuth flow involves:

  1. An unauthenticated user arrives at myapp.com, and the oauth filter redirects them to the authorization_endpoint for login. The client ID is sent in the query string in this first redirect.
  2. After a successful login, the authn server should be configured to redirect the user back to myapp.com/callback, assuming you have chosen /callback as your match path inside the Envoy config. An "authorization grant" is included in the query string for this second redirect.
  3. Using this new grant and the token_secret, the filter then attempts to retrieve an access token from the token_endpoint. The filter knows to do this instead of reinitiating another login because the incoming request has a path that matches the redirect_path_matcher criteria.

So if you're getting "OAuth flow failed" at /callback, I assume your token endpoint is denying your configured secret or the grant obtained in step 2.

You may also notice that hmac_secret is never mentioned in that flow - the HMAC is important because the filter, upon a full completion of the OAuth flow, can set cookies as a method of convenience to the user and bypass future login requirements until a certain expiry. To prevent the risk of a request injecting these cookies manually and skipping OAuth freely, we require another secret to help derive an HMAC from the access token, host header, etc., ensuring cookie integrity.

andreyprezotto commented 3 years ago

@juanvasquezreyes thanks for your reply. Do you mind posting your current full confg so I can take a look into it? Thanks

juanvasquezreyes commented 3 years ago

@juanvasquezreyes thanks for your reply. Do you mind posting your current full confg so I can take a look into it? Thanks

sure, but is not working as I want

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901
static_resources:
  listeners:
  - name: oauth
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 443
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          tracing: {}
          codec_type: "AUTO"
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
              - name: local_service
                domains:
                  - '*'
                routes:
                  - match:
                     prefix: /callback
                    route:
                      cluster: myapp
          http_filters:
          - name: envoy.filters.http.oauth2
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2
              config:
                token_endpoint:
                  cluster: oauth
                  uri: <keycloak-server>/token
                  timeout: 3s
                authorization_endpoint: <keycloak-server>/auth
                redirect_uri: "<envoyserver>/callback"
                redirect_path_matcher:
                  path:
                    exact: /callback
                signout_path:
                  path:
                    exact: /signout
                credentials:
                  client_id: <client id>
                  token_secret:
                    name: token
                    sds_config:
                      path: "/etc/tokensecret.yaml"
                  hmac_secret:
                    name: hmac
                    sds_config:
                      path: "/etc/hmacsecret.yaml"
          - name: envoy.router
      tls_context:
        common_tls_context:
          tls_certificates:
            - certificate_chain:
                filename: "cert.pem"
              private_key:
                filename: "key.pem"
  clusters:
  - name: oauth
    connect_timeout: 5s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: auth
      endpoints:
      - lb_endpoints:
        - endpoint:
            address: { socket_address: { address: <keycloak-server>, port_value: 443 }}
    tls_context: { sni: <keycloak-server> }

 - name: myapp
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: myapp
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: myapp.com
                port_value: 443

in the tokensecret.yaml I have this, and its similar to what I have in hmacsecret.yaml

resources:
  - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"
    name: token
    generic_secret:
      secret:
        inline_string: <tokensecret>

and when I go to https://envoyserver it is redirecting to my keycloak and after authenticating Its redirecting to https://envoyserver/callback and is supposed to be sending to myapp but instead of that I'm getting this error "OAuth flow failed."

andreyprezotto commented 3 years ago

@juanvasquezreyes using your config file I'm now able to run envoy, at least. Thank you!. But it is not working yet. I'm running the docker image locally, and when I request localhost:8080 I get an empty response from the server (envoy)...this is not even going through the filter yet. And I don't see errors in the log.

juanvasquezreyes commented 3 years ago

@juanvasquezreyes using your config file I'm now able to run envoy, at least. Thank you!. But it is not working yet. I'm running the docker image locally, and when I request localhost:8080 I get an empty response from the server (envoy)...this is not even going through the filter yet. And I don't see errors in the log.

that is because the listener I'm using is not using port 8080

krubot commented 3 years ago

So I've been working on getting keycloak integration with envoy for a while and have created a repo to get a poc out for getting this to work:

https://github.com/krubot/envoy-keycloak-oauth

However, from the envoy logs when running this I get the following output which seems to be causing the OAuth flow failed response:

proxy       | [2020-10-25 15:33:05.628][29][debug][router] [source/common/router/router.cc:429] [C0][S2257522640722456408] cluster 'keycloak' match for URL '/auth/realms/api/protocol/openid-connect/token'
proxy       | [2020-10-25 15:33:05.628][29][debug][router] [source/common/router/router.cc:586] [C0][S2257522640722456408] router decoding headers:
proxy       | ':path', '/auth/realms/api/protocol/openid-connect/token'
proxy       | ':authority', '127.0.0.1:8080'
proxy       | ':method', 'POST'
proxy       | ':scheme', 'http'
proxy       | 'content-type', 'application/x-www-form-urlencoded'
proxy       | 'x-envoy-internal', 'true'
proxy       | 'x-forwarded-for', '172.24.0.5'
proxy       | 'x-envoy-expected-rq-timeout-ms', '5000'
proxy       |
proxy       | [2020-10-25 15:33:05.628][29][debug][pool] [source/common/http/conn_pool_base.cc:71] queueing stream due to no available connections
proxy       | [2020-10-25 15:33:05.628][29][debug][pool] [source/common/conn_pool/conn_pool_base.cc:104] creating a new connection
proxy       | [2020-10-25 15:33:05.628][29][debug][client] [source/common/http/codec_client.cc:39] [C1] connecting
proxy       | [2020-10-25 15:33:05.628][29][debug][connection] [source/common/network/connection_impl.cc:755] [C1] connecting to 172.24.0.3:8080
proxy       | [2020-10-25 15:33:05.628][29][debug][connection] [source/common/network/connection_impl.cc:771] [C1] connection in progress
proxy       | [2020-10-25 15:33:05.629][29][debug][connection] [source/common/network/connection_impl.cc:611] [C1] connected
proxy       | [2020-10-25 15:33:05.629][29][debug][client] [source/common/http/codec_client.cc:77] [C1] connected
proxy       | [2020-10-25 15:33:05.629][29][debug][pool] [source/common/conn_pool/conn_pool_base.cc:205] [C1] attaching to next stream
proxy       | [2020-10-25 15:33:05.629][29][debug][pool] [source/common/conn_pool/conn_pool_base.cc:126] [C1] creating stream
proxy       | [2020-10-25 15:33:05.629][29][debug][router] [source/common/router/upstream_request.cc:357] [C0][S2257522640722456408] pool ready
proxy       | [2020-10-25 15:33:05.647][29][debug][router] [source/common/router/router.cc:1178] [C0][S2257522640722456408] upstream headers complete: end_stream=false
proxy       | [2020-10-25 15:33:05.647][29][debug][http] [source/common/http/async_client_impl.cc:100] async http request response headers (end_stream=false):
proxy       | ':status', '400'
proxy       | 'cache-control', 'no-store'
proxy       | 'x-xss-protection', '1; mode=block'
proxy       | 'pragma', 'no-cache'
proxy       | 'x-frame-options', 'SAMEORIGIN'
proxy       | 'referrer-policy', 'no-referrer'
proxy       | 'date', 'Sun, 25 Oct 2020 15:33:05 GMT'
proxy       | 'connection', 'keep-alive'
proxy       | 'strict-transport-security', 'max-age=31536000; includeSubDomains'
proxy       | 'x-content-type-options', 'nosniff'
proxy       | 'content-type', 'application/json'
proxy       | 'content-length', '84'
proxy       | 'x-envoy-upstream-service-time', '18'
proxy       |
proxy       | [2020-10-25 15:33:05.647][29][debug][client] [source/common/http/codec_client.cc:109] [C1] response complete
proxy       | [2020-10-25 15:33:05.647][29][debug][upstream] [source/extensions/filters/http/oauth2/oauth_client.cc:67] Oauth response code: 400
proxy       | [2020-10-25 15:33:05.647][29][debug][http] [source/common/http/filter_manager.cc:805] [C0][S2272988063471314909] Sending local reply with details
proxy       | [2020-10-25 15:33:05.647][29][debug][http] [source/common/http/conn_manager_impl.cc:1435] [C0][S2272988063471314909] encoding headers via codec (end_stream=false):
proxy       | ':status', '401'
proxy       | 'content-length', '18'
proxy       | 'content-type', 'text/plain'
proxy       | 'date', 'Sun, 25 Oct 2020 15:33:05 GMT'
proxy       | 'server', 'envoy'
proxy       |
proxy       | [2020-10-25 15:33:05.647][29][debug][pool] [source/common/http/http1/conn_pool.cc:50] [C1] response complete
proxy       | [2020-10-25 15:33:05.647][29][debug][pool] [source/common/conn_pool/conn_pool_base.cc:151] [C1] destroying stream: 0 remaining

Not sure why I'm getting back a 400 response here but this looks to be in issue.

williamsfu99 commented 3 years ago

@krubot Looks like the OAuth filter's token_endpoint client was committed with some bugs. I'll send a PR today with the promised documentation.

andreyprezotto commented 3 years ago

@juanvasquezreyes using your config file I'm now able to run envoy, at least. Thank you!. But it is not working yet. I'm running the docker image locally, and when I request localhost:8080 I get an empty response from the server (envoy)...this is not even going through the filter yet. And I don't see errors in the log.

that is because the listener I'm using is not using port 8080

I changed to use 8080 in my config file, and I have and app listening to this port. I see this warning in envoy logs: [warning][config] [source/common/config/filesystem_subscription_impl.cc:43] Filesystem config update rejected: Unexpected SDS secret (expecting hmac): token

krsri commented 3 years ago

@krubot

Checked from the master am still getting "Oauth response code: 400". Please update here if it is working for you. Thanks I used "envoyproxy/envoy-dev" docker image.

krsri commented 3 years ago

@juanvasquezreyes using your config file I'm now able to run envoy, at least. Thank you!. But it is not working yet. I'm running the docker image locally, and when I request localhost:8080 I get an empty response from the server (envoy)...this is not even going through the filter yet. And I don't see errors in the log.

that is because the listener I'm using is not using port 8080

I changed to use 8080 in my config file, and I have and app listening to this port. I see this warning in envoy logs: [warning][config] [source/common/config/filesystem_subscription_impl.cc:43] Filesystem config update rejected: Unexpected SDS secret (expecting hmac): token

Check whether you are using the same name in both tokensecret.yaml and hmac.yaml, If yes change the name .

juanvasquezreyes commented 3 years ago

I just tested it and looks like is working for me this is the full configuration file I'm using, its using port 443, if you need another port just change it

I'm using this image to test envoyproxy/envoy-dev

in your oauth server you have to put as valid redirect url the server where envoy is running

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901
static_resources:
  secrets:
  - name: token
    generic_secret:
      secret:
        inline_string: <token_secret>
  - name: hmac
    generic_secret:
      secret:
        inline_string: <hmac>
  listeners:
  - name: oauth
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 443
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          tracing: {}
          codec_type: "AUTO"
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
              - name: local_service
                domains:
                  - '*'
                routes:
                  - match:
                     prefix: /
                    route:
                      cluster: myapp
          http_filters:          
          - name: envoy.filters.http.oauth2
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2
              config:
                token_endpoint:
                  cluster: oauth
                  uri: <oauth_server>/openid-connect/token/
                  timeout: 3s
                authorization_endpoint: <oauth_server>/openid-connect/auth
                redirect_uri: "https://<envoy_server>/callback"
                redirect_path_matcher:
                  path:
                    exact: /callback
                signout_path:
                  path:
                    exact: /signout
                credentials:
                  client_id: <clien_id>
                  token_secret:
                    name: token
                  hmac_secret:
                    name: hmac
          - name: envoy.router
      tls_context: # THIS IS FOR HTTPS
        common_tls_context:
          tls_certificates:
            - certificate_chain:
                filename: "/etc/server.pem"
              private_key:
                filename: "/etc/key.pem"
  clusters:
  - name: myapp
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: myapp
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: myapp.com
                port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext
        sni: myapp.com

  - name: oauth
    connect_timeout: 5s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: oauth
      endpoints:
      - lb_endpoints:
        - endpoint:
            address: { socket_address: { address: <oauth_server>, port_value: 443 }}
    tls_context: { sni: <oauth_server> }
riptl commented 3 years ago

With Envoy version: e98e41a8e168af7acae8079fc0cd68155f699aa3/1.16.2/clean-getenvoy-be6132a-envoy/RELEASE/BoringSSL I can confirm that the implementation of token_endpoint is broken.

I configured a dummy socat to listen on localhost, and configured token_endpoint to fire against it. It never sends any form parameters for the token request:

> 2020/12/29 23:31:47.578765  length=228 from=0 to=227
POST /login/oauth/access_token HTTP/1.1\r
host: localhost:51000\r
content-type: application/x-www-form-urlencoded\r
x-envoy-internal: true\r
x-forwarded-for: <redacted>\r
x-envoy-expected-rq-timeout-ms: 3000\r
content-length: 0\r
\r
riptl commented 3 years ago

Please ignore above, this has been fixed with version: db57a82d9fecd8d4a525f0443d8512b4ad9ab485/1.17.0-dev/clean-getenvoy-be6132a-envoy/RELEASE/BoringSSL and token_endpoint requests now come with a form

MichaelSasser commented 1 year ago

This is how I solved it using file based xDS:

Envoy

LDS

# [...]
            http_filters:
              - name: envoy.filters.http.oauth2
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3.OAuth2
                  config:
                    token_endpoint:
                      cluster: proxy-oidc
                      uri: "https://acme.example.com/realms/MyKeycloakRealm/protocol/openid-connect/token"
                      timeout: 3s
                    authorization_endpoint: "https://acme.example.com/realms/MyKeycloakRealm/protocol/openid-connect/auth"
                    redirect_uri: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/callback"
                    redirect_path_matcher:
                      path:
                        exact: /callback
                    signout_path:
                      path:
                        exact: /signout
                    credentials:
                      client_id: my_service
                      token_secret:
                        name: "my_service_oidc_token"
                        sds_config:
                          resource_api_version: V3
                          path_config_source:
                            path: "path/to/secrets/dir/secret_my_service_oidc_token.yaml"
                            watched_directory:
                              path: "/path/to/watch/dir"
                      hmac_secret:
                        name: "my_service_oidc_hmac"
                        sds_config:
                          resource_api_version: V3
                          path_config_source:
                            path: "/path/to/secrets/dir/secret_my_service_oidc_hmac.yaml"
                            watched_directory:
                              path: "/path/to/watch/dir"
                    auth_scopes:
                      - openid
# [...]

SDS

---
# path/to/secrets/dir/secret_my_service_oidc_token.yaml
resources:
  - name: "my_service_oidc_token"
    "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"
    generic_secret:
      secret:
        inline_string: "YOUR_SECRET"
---
# path/to/secrets/dir/secret_my_service_oidc_hmac.yaml
resources:
  - name: "my_service_oidc_hmac"
    "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"
    generic_secret:
      secret:
        # generate: `head -c 32 /dev/urandom | base64`
        inline_string: "randomly generated secret (base64)"

CDS

---

resources:
  - name: "proxy-oidc"
    "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
    type: STRICT_DNS
    connect_timeout: 15s
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: "proxy-keycloak-oidc"
      endpoints:
        - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: "123.123.123.123" 
                    port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        sni: "acme.example.com"
    # - name: "my_service" [...]

Keycloak Client

{
  "clientId": "my_service",
  "name": "My Service",
  "description": "My Service Description",
  "rootUrl": "https://my_service.example.com",
  "adminUrl": "https://my_service.example.com/admin",
  "baseUrl": "",
  "surrogateAuthRequired": false,
  "enabled": true,
  "alwaysDisplayInConsole": false,
  "clientAuthenticatorType": "client-secret",
  "secret": "YOUR_SECRET",
  "redirectUris": [
    "https://my_service.example.com/callback"
  ],
  "webOrigins": [
    "https://my_service.example.com"
  ],
  "notBefore": 0,
  "bearerOnly": false,
  "consentRequired": false,
  "standardFlowEnabled": true,
  "implicitFlowEnabled": false,
  "directAccessGrantsEnabled": true,
  "serviceAccountsEnabled": true,
  "authorizationServicesEnabled": true,
  "publicClient": false,
  "frontchannelLogout": true,
  "protocol": "openid-connect",
  "attributes": {
    "oidc.ciba.grant.enabled": "false",
    "client.secret.creation.time": "1689250398",
    "backchannel.logout.session.required": "true",
    "post.logout.redirect.uris": "https://my_service.example.com/logout",
    "display.on.consent.screen": "false",
    "oauth2.device.authorization.grant.enabled": "false",
    "backchannel.logout.revoke.offline.tokens": "false"
  },
  "authenticationFlowBindingOverrides": {},
  "fullScopeAllowed": true,
  "nodeReRegistrationTimeout": -1,
  "protocolMappers": [
    {
      "name": "Client Host",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usersessionmodel-note-mapper",
      "consentRequired": false,
      "config": {
        "user.session.note": "clientHost",
        "id.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "clientHost",
        "jsonType.label": "String"
      }
    },
    {
      "name": "Client ID",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usersessionmodel-note-mapper",
      "consentRequired": false,
      "config": {
        "user.session.note": "client_id",
        "id.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "client_id",
        "jsonType.label": "String"
      }
    },
    {
      "name": "Client IP Address",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usersessionmodel-note-mapper",
      "consentRequired": false,
      "config": {
        "user.session.note": "clientAddress",
        "id.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "clientAddress",
        "jsonType.label": "String"
      }
    }
  ],
  "defaultClientScopes": [
    "web-origins",
    "acr",
    "roles",
    "profile",
    "email"
  ],
  "optionalClientScopes": [
    "address",
    "phone",
    "offline_access",
    "microprofile-jwt"
  ],
  "access": {
    "view": true,
    "configure": true,
    "manage": true
  }
}
ignashkin commented 1 year ago

Hello everyone! I have the same problem with envoy:1.27.0 and okta. Envoy received 401 from token url. I've performed some experience end found out that a problem with header 'scheme: http'. There is a log: ` [2023-08-10 10:43:35.268][28][debug][oauth2] [source/extensions/filters/http/oauth2/filter.cc:397] can not skip oauth flow [2023-08-10 10:43:35.268][28][debug][oauth2] [source/extensions/filters/http/oauth2/oauth_client.cc:58] Dispatching OAuth request for access token. [2023-08-10 10:43:35.268][28][debug][router] [source/common/router/router.cc:478] [Tags: "ConnectionId":"0","StreamId":"9004732378072910772"] cluster 'oauth' match for URL '/oauth/token' [2023-08-10 10:43:35.268][28][debug][router] [source/common/router/router.cc:690] [Tags: "ConnectionId":"0","StreamId":"9004732378072910772"] router decoding headers: ':path', '/oauth/token' ':authority', 'dev-my-instance.us.auth0.com' ':method', 'POST' ':scheme', 'http' 'content-type', 'application/x-www-form-urlencoded' 'accept', 'application/json' 'content-length', '307' 'x-envoy-internal', 'true' 'x-forwarded-for', '10.4.174.54' 'x-envoy-expected-rq-timeout-ms', '5000'

[2023-08-10 10:43:35.268][28][debug][pool] [source/common/http/conn_pool_base.cc:78] queueing stream due to no available connections (ready=0 busy=0 connecting=0) [2023-08-10 10:43:35.268][28][debug][pool] [source/common/conn_pool/conn_pool_base.cc:291] trying to create new connection [2023-08-10 10:43:35.268][28][debug][pool] [source/common/conn_pool/conn_pool_base.cc:145] creating a new connection (connecting=0) [2023-08-10 10:43:35.268][28][debug][connection] [./source/common/network/connection_impl.h:98] [Tags: "ConnectionId":"14"] current connecting state: true [2023-08-10 10:43:35.268][28][debug][client] [source/common/http/codec_client.cc:57] [Tags: "ConnectionId":"14"] connecting

[2023-08-10 10:43:35.268][28][debug][connection] [source/common/network/connection_impl.cc:972] [Tags: "ConnectionId":"14"] immediate connect error: Network is unreachable [2023-08-10 10:43:35.268][28][debug][connection] [source/common/network/connection_impl.cc:587] [Tags: "ConnectionId":"14"] raising immediate error [2023-08-10 10:43:35.268][28][debug][connection] [source/common/network/connection_impl.cc:250] [Tags: "ConnectionId":"14"] closing socket: 0 [2023-08-10 10:43:35.268][28][debug][client] [source/common/http/codec_client.cc:107] [Tags: "ConnectionId":"14"] disconnect. resetting 0 pending requests [2023-08-10 10:43:35.268][28][debug][pool] [source/common/conn_pool/conn_pool_base.cc:484] [Tags: "ConnectionId":"14"] client disconnected, failure reason: immediate connect error: Network is unreachable [2023-08-10 10:43:35.268][28][debug][router] [source/common/router/router.cc:1281] [Tags: "ConnectionId":"0","StreamId":"9004732378072910772"] upstream reset: reset reason: remote connection failure, transport failure reason: immediate connect error: Network is unreachable [2023-08-10 10:43:35.268][28][debug][http] [source/common/http/async_client_impl.cc:123] async http request response headers (end_stream=false): ':status', '503' 'content-length', '173' 'content-type', 'text/plain'

[2023-08-10 10:43:35.268][28][debug][oauth2] [source/extensions/filters/http/oauth2/oauth_client.cc:87] Oauth response code: 503 [2023-08-10 10:43:35.269][28][debug][oauth2] [source/extensions/filters/http/oauth2/oauth_client.cc:88] Oauth response body: upstream connect error or disconnect/reset before headers. reset reason: remote connection failure, transport failure reason: immediate connect error: Network is unreachable [2023-08-10 10:43:35.269][28][debug][http] [source/common/http/filter_manager.cc:1003] [Tags: "ConnectionId":"11","StreamId":"10311191599645750914"] Sending local reply with details [2023-08-10 10:43:35.269][28][debug][http] [source/common/http/conn_manager_impl.cc:1756] [Tags: "ConnectionId":"11","StreamId":"10311191599645750914"] encoding headers via codec (end_stream=false): ':status', '401' 'content-length', '18' 'content-type', 'text/plain' 'date', 'Thu, 10 Aug 2023 10:43:34 GMT' 'server', 'envoy'

[2023-08-10 10:43:35.269][28][debug][http] [source/common/http/conn_manager_impl.cc:1861] [Tags: "ConnectionId":"11","StreamId":"10311191599645750914"] Codec completed encoding stream. `

I've tried execute the same query by curl without header 'scheme' and I received token. But If I add header 'scheme' I receive the same error

ignashkin commented 1 year ago

Also, I tried using keycloak instead of okta (oath0.com) and it works. But I have to use okta.

flashpaper12 commented 10 months ago

@ignashkin I've been encountering this exact problem. Have you found any workarounds?

busgo commented 8 months ago

热门团队

我正在尝试使用 envoy 1.16.0 实现 oauth 我阅读了以下链接提供的文档https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/oauth2_filter,我不认为文档是正确的,因为我尝试使用示例描述,但它不起作用

这是我面临的错误,您能否更新文档并提供一个很好的示例?

无法将 JSON 解析为原型(INVALID_ARGUMENT:(http_filters[0].typed_config): 值无效类型 URL 无效,类型未知:envoy.extensions.filters.http.oauth2.v3alpha.OAuth2 For type Any):{"clusters": [{"lb_policy":"ROUND_ROBIN","load_assignment":{"endpoints":[{"lb_endpoints":[{"endpoint":{"address":{"socket_address":{"address":"auth.example .com","port_value":443}}}}]}],"cluster_name":"auth"},"name":"auth","tls_context":{"sni":"auth.example.com" },"type":"LOGICAL_DNS","connect_timeout":"5s"}],"static_resources":{"listeners":[{"address":{"socket_address":{"address":"0.0.0.0" ,"port_value":443}},"name":"oauth","filter_chains":[{"filters":[{"name":"envoy.filters.network.http_connection_manager","typed_config":{"http_filters ":[{"typed_config":{"config":{"redirect_uri":"%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback","signout_path":{"路径":{"exact":"/signout"}},"credentials":{"client_id":"foo"},"token_endpoint":{"uri":"oauth.com/token","cluster": "oauth","timeout":"3s"},"redirect_path_matcher":{"path":{"exact":"/callback"}},"authorization_endpoint":"https://oauth.com/oauth/authorize /"},"@type":"type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2"},"name":"envoy.filters.http.oauth2"},{"name":"envoy.router" }],”@type":"type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"}}]}]}]}}

这是我的配置文件

static_resources:
  listeners:
  - name: oauth
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 443
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          http_filters:
          - name: envoy.filters.http.oauth2
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2
              config:
                token_endpoint:
                  cluster: oauth
                  uri: oauth.com/token
                  timeout: 3s
                authorization_endpoint: https://oauth.com/oauth/authorize/
                redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback"
                redirect_path_matcher:
                  path:
                    exact: /callback
                signout_path:
                  path:
                    exact: /signout
                credentials:
                  client_id: foo
                  token_secret:
                    name: token
                    sds_config:
                      path: "/etc/envoy/token-secret.yaml"
          - name: envoy.router

clusters:
- name: auth
  connect_timeout: 5s
  type: LOGICAL_DNS
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: auth
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: auth.example.com
              port_value: 443
  tls_context:
    sni: auth.example.com

令牌文件还有什么特殊格式吗?或者我们如何支付令牌秘密?

now, new release version you must use "type.googleapis.com/envoy.extensions.filters.http.oauth2.v3.OAuth2" is not "type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2"

flashpaper12 commented 6 months ago

Hi @ignashkin, did you manage to solve this issue?

ps-walroujoulah commented 3 months ago

Hi @ignashkin,

I faced the same issue and spent some time digging through debug logs to understand the problem. The root cause was that Envoy was trying to connect to the OIDC server using IPv6, which led to the incorrect use of the HTTP scheme instead of HTTPS. This behavior stems from Envoy's default DNS resolution preference, which is to prefer IPv6 and fall back to IPv4. This can be adjusted by setting the dns_lookup_family field in the cluster configuration.

To resolve this, I adjusted the cluster configuration to force IPv4 only. Here’s the configuration that worked for me:

- name: auth
  connect_timeout: 2s
  type: STRICT_DNS
  lb_policy: ROUND_ROBIN
  dns_lookup_family: V4_ONLY
  load_assignment:
    cluster_name: auth
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: <server>
              port_value: 443
  transport_socket:
    name: envoy.transport_sockets.tls
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext

This configuration forces Envoy to use IPv4 (specified by dns_lookup_family: V4_ONLY), ensuring that the connection scheme remains HTTPS.

easternmotors commented 3 months ago

Hi @ignashkin,

I faced the same issue and spent some time digging through debug logs to understand the problem. The root cause was that Envoy was trying to connect to the OIDC server using IPv6, which led to the incorrect use of the HTTP scheme instead of HTTPS. This behavior stems from Envoy's default DNS resolution preference, which is to prefer IPv6 and fall back to IPv4. This can be adjusted by setting the dns_lookup_family field in the cluster configuration.

To resolve this, I adjusted the cluster configuration to force IPv4 only. Here’s the configuration that worked for me:

- name: auth
  connect_timeout: 2s
  type: STRICT_DNS
  lb_policy: ROUND_ROBIN
  dns_lookup_family: V4_ONLY
  load_assignment:
    cluster_name: auth
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: <server>
              port_value: 443
  transport_socket:
    name: envoy.transport_sockets.tls
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext

This configuration forces Envoy to use IPv4 (specified by dns_lookup_family: V4_ONLY), ensuring that the connection scheme remains HTTPS.

@ps-walroujoulah thanks for this! I was debugging this for a while and just stumbled upon your post -- fixed my issue connecting to my service.