Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic HTTP Config supports OAuth1 #1297

Closed
JakeQuilty opened this issue Jul 6, 2020 · 3 comments
Closed

Generic HTTP Config supports OAuth1 #1297

JakeQuilty opened this issue Jul 6, 2020 · 3 comments

Comments

@JakeQuilty
Copy link
Contributor

JakeQuilty commented Jul 6, 2020

Is your feature request related to a problem? Please describe.

Currently Secretless Broker does not support any of the 3 methods of authenticating with OAuth1

Problems with OAuth 1.0a and Secretless

OAuth 1.0a requires 7 different parameters to authenticate:

Authorization:  oauth_consumer_key="0685bd9184jfhq22",
                oauth_token="ad180jjd733klru7",
                oauth_signature_method="HMAC-SHA1",
                oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
                oauth_timestamp="137131200",
                oauth_nonce="4572616e48616d6d65724c61686176",
                oauth_version="1.0"

consumer_key - Constant secret
nonce - Dynamic, created specifically for request
signature - Dynamic, generated from all other parameters
timestamp - Dynamic, timestamp of request and will be denied if too old when request is sent
token - Constant secret
signature_method - Dynamic
version - Dynamic

There are 3 ways to pass these parameters in the HTTP request:

1. In the HTTP Authorization header as defined in OAuth HTTP Authorization Scheme.
2. As the HTTP POST request body with a content-type of application/x-www-form-urlencoded.
3. Added to the URLs in the query part (as defined by RFC3986 section 3).

Method 1

Only 2 of the 7 parameters are constant secrets for Secretless to inject, the rest need to be created by the application when making the request.
The main problem with OAuth 1.0a and Secretless is that Secretless currently replaces the whole Authorization line in the header, with no way to "save" the other parameters that are passed.

Example:

Request:

http_proxy=localhost:8071 curl -k --request POST \
  --url 'test:4443' \
  --header 'authorization: OAuth oauth_consumer_key="CONSUMER_API_KEY", oauth_nonce="OAUTH_NONCE", oauth_signature="OAUTH_SIGNATURE", oauth_signature_method="HMAC-SHA1", oauth_timestamp="OAUTH_TIMESTAMP", oauth_token="ACCESS_TOKEN", oauth_version="1.0"' \

Secretless generic config:

...
config:
      headers:
        Authorization: ApiKey test
      forceSSL: true
      authenticateURLsMatching:
        - ^http[s]*

Request sent to server:

Host: test:4443
User-Agent: curl/7.64.1
Content-Length: 0
Accept: */*
Authorization: ApiKey test
Accept-Encoding: gzip

I couldn't find a way to inject the secrets that the request needs for authentication and keep the other parameters at the same time.

Method 2

Currently modifications to request bodies are not supported by Secretless.

Method 3

This is not currently supported either

Recommended Solution

I think the best solution to this, would be to add a feature that allows Secretless to inject secrets into the header, while keeping the other parameters in the authorization line.

EDIT: This won't work for OAuth_signatures that use the tokens that Secretless keeps in the OAuth_signature base string. The best solution might be adding the ability to create all of the parameters needed for OAuth1 in Secretless and add the ability to specify the site specific signature format in the generic config

Additional context

Stemmed from: #1264

@izgeri
Copy link
Contributor

izgeri commented Jul 6, 2020

@JakeQuilty after reading that I have what may end up being a silly question - but can you have your secretless config hardcode the values in the string here: OAuth oauth_consumer_key="CONSUMER_API_KEY", oauth_nonce="OAUTH_NONCE", oauth_signature="OAUTH_SIGNATURE", oauth_signature_method="HMAC-SHA1", oauth_timestamp="OAUTH_TIMESTAMP", oauth_token="ACCESS_TOKEN", oauth_version="1.0" or get them from the env instead of from a secret store, and then set up the header to include all of them?

ie if the header is supposed to be Authorization: {string from above} can you have secretless config be

version: 2
services:
  generic-oauth1:
    connector: generic_http
    listenOn: tcp://0.0.0.0:8071
    credentials:
      consumer_key:
        from: keychain
        get: service#generic/consumer-key
      token:
        from: keychain
        get: service#generic/token
      nonce:
        from: environment
        get: OAUTH_NONCE
      signature:
        from: environment
        get: OAUTH_SIGNATURE
      signature_method: HMAC-SHA1
      timestamp:
        from: environment
        get: OAUTH_TIMESTAMP

    config:
      headers:
        Authorization: OAuth oauth_consumer_key="{{ .consumer_key }}", oauth_nonce="{{ .nonce }}", oauth_signature="{{ .signature }}", oauth_signature_method="{{ .signature_method }}", oauth_timestamp="{{ .timestamp }}", oauth_token="{{ .token }}", oauth_version="1.0"
      forceSSL: true
      authenticateURLsMatching:
        - ^http[s]*

I’m not sure the specific syntax of the Authorization line should work, but either something very much like it should or we should enhance the generic connector. As possible enhancements, we could define functions for the nonce, timestamp, and hash_hmac signature hashing function that twitter uses.

Note: updating to add that the signature requires info on the token, so Secretless would almost certainly have to compute this in order to get this to work.

@mdodell
Copy link
Contributor

mdodell commented Jul 30, 2020

Proposed Changes

Our plan is to add OAuth1.0 to the generic HTTP connector.

Plan

We are planning to create a new addition to the generic HTTP Connector, by adding an oauth1Params option to the config. The logic for this new connector will sit as a protocol in internal/plugins/connectors/http/generic/oauthprotocol.

We will call a new method, called CreateOAuthHeader that defines a header that looks like this example taken from the Twitter documentation:

OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg", oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1318622958", oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb", oauth_version="1.0"

Our plan is to follow the OAuth protocol, as defined here. Our method CreateOAuthHeader in outhprotocol will do three things:

  1. Create a Base String
  2. Create a signature key using HMAC-SHA1
  3. Return the string to be added as a header.

oauth1Params requires four pieces of information:

  1. oauth_consumer_key
  2. oauth_consumer_secret
  3. oauth_token
  4. oauth_token_secret

Potential edge cases to test for include overriding the Header if the user tries to pass in an Authentication header through Secretless.

Below is what an example of a YAML file that uses this new functionality of the generic HTTP connector would like look like.

Example Config

version: 2
services:
 a-service-name:
    connector: generic_http
    listenOn: tcp://0.0.0.0:8021
    credentials:
      consumer_key:
        from: keychain
        get: service#generic/consumer-key
      consumer_secret:
        from: keychain
        get: service#generic/consumer-key-secret
      token:
        from: keychain
        get: service#generic/token
      token_secret:
        from: keychain
        get: service#generic/token-secret
    config:
      headers: 
         # if oauth1Params exists in the config, we will add the Authorization header programatically.
         Authorization: "THIS WILL BE WRITTEN OVER” 
      oauth1Params:
        oauth_consumer_key: {{ .consumer_key }}
        oauth_consumer_secret: {{ .consumer_secret }}
        oauth_token: {{ .token }}
        oauth_token_secret: {{ .token_secret }}”
      forceSSL: true
      authenticateURLsMatching:
        - ^http[s]*

Topics for Discussion:

  1. Should we only support HMAC-SHA1? Other methods of encoding a signature are RSA-SHA1 and PLAINTEXT. If we want to support these, then the user would pass that in oauth1Params in the YAML file as well.
  2. What should happen if an Authorization header is passed in, but is overridden by the oauth1Params? Should we write a warning, or throw an error?

To-Do (edited)

@izgeri
Copy link
Contributor

izgeri commented Aug 11, 2020

I'm going to leave this open until we create a new Secretless tag (hopefully soon). For now, these changes are merged to master.

@izgeri izgeri reopened this Aug 11, 2020
@izgeri izgeri closed this as completed Sep 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants