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

Support connecting to external HTTP APIs via the Powerbox #2870

Merged
merged 22 commits into from
Mar 1, 2017

Conversation

kentonv
Copy link
Member

@kentonv kentonv commented Feb 16, 2017

All requests for an ApiSession will offer the option to specify an HTTP URL manually. If the user specifies a URL containing a username/password or a webkey, the authorization mechanism is understood and used (without passing details to the app).

Additionally, requests with an HTTP canonicalUrl in their tag will offer a one-click option to use that URL. If the canonicalUrl refers to a well-known OAuth API service (currently recognizes Github and some Google API hosts), an OAuth handshake will occur.

Copy link
Collaborator

@dwrensha dwrensha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty exciting!

function registerHttpApiFrontendRef(registry) {
registry.register({
frontendRefField: "http",
typeId: "14445827391922490823",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why write this out rather than ApiSession.typeId?

if ("none" in request.auth) {
request.auth = { basic: { username: parts[0], password: parts[1] } };
} else {
throw new Meteor.Error(400, "Can't supprot multiple authentication mechanisms at once");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/supprot/support/

});
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this http-arbitrary option intended to be in an else block? If not, then it probably deserves a comment about why it is always returned.

# See the License for the specific language governing permissions and
# limitations under the License.

# This is used specifically with hack-session.capnp.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment a copy-pasto?

// auth :union {
// none :Void;
// bearer :Text;
// basic :group { username :Text; password :Text; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish we could encrypt these fields somehow. I don't see a good way to do that, though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could encrypt them using the ApiToken as a key, since we only store the hash of the API token in the database. Child ApiTokens would store an encrypted copy of the parent token (in addition to the parent token ID as they do today). But a complete solution would require that app storage be encrypted as well, since that's where the tokens are ultimately stored. That is, of course, a big project...

+ "&grant_type=refresh_token",
});

console.log(response.data);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray debug print?

@@ -426,6 +426,14 @@ ApiTokens = new Mongo.Collection("apiTokens", collectionOptions);
// verifiedEmail: An VerifiedEmail capability that is implemented by the frontend.
// An object containing `verifierId`, `tabId`, and `address`.
// identity: An Identity capability. The field is the identity ID.
// http: An ApiSession capability pointing to an external HTTP service. Object containing:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where in the UI will the user be able to audit such frontendRefs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I don't plan to implement that in this PR, but it's important.

@kentonv
Copy link
Member Author

kentonv commented Feb 17, 2017

OK, I added a bunch of commits to implement encryption of these HTTP credentials!

Caveat: The encryption key is the token given to the grain. So, if the grain stores that token unencrypted, and if an attacker gets a copy of the grain's storage and a copy of Sandstorm's Mongo database, then the attacker can decrypt the credentials.

However, an attacker who gets only the Mongo database cannot decrypt any credentials within it. I suspect that getting the database and grain storage is in many cases significantly harder than getting just the database. For example, it's easy to imagine a Mongo injection bug in the Sandstorm shell which allows an attacker to get access to all ApiTokens, but it's harder to imagine a bug in which Sandstorm delivers raw grain storage data to attackers.

Some day, we will implement transparent encrypted grain storage in Blackrock, which will take things a step further.

// strings, hence can be used directly as the key. No MAC is applied, because this scheme is not
// intended to protect against attackers who have write access to the database -- such an attacker
// could almost certainly do more damage by modifying the non-encrypted fields anyway. (Put another
// way, if we wanted to MAC something, we'd needto MAC the entire ApiToken structure, not just
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/needto/need to/

@@ -426,13 +426,33 @@ ApiTokens = new Mongo.Collection("apiTokens", collectionOptions);
// verifiedEmail: An VerifiedEmail capability that is implemented by the frontend.
// An object containing `verifierId`, `tabId`, and `address`.
// identity: An Identity capability. The field is the identity ID.
// http: An ApiSession capability pointing to an external HTTP service. Object containing:
// url: Base URL of the external service.
// auth: Authentitation mechanism. Object containing one of:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Authentication"

if ("none" in request.auth) {
request.auth = { bearer: parsedUrl.hash.slice(1) };
} else {
throw new Meteor.Error(400, "Can't supprot multiple authentication mechanisms at once");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"support"

So far, this includes:
* Offering the canonicalUrl as a one-click option, but with no authentication.
* Offering an option to enter a URL manually.
  * If the URL contains username/password or is a webkey, the session will be authenticated as specified.
…on in admin-server.js.

This dependency is now explicit, so this hack isn't needed anymore. (Why it was ever used, I'm not sure.)
Main problem is that various methods that previously accepted either a String or a Buffer as a SturdyRef representation are now more strict.

I wish we had a type system.
@kentonv
Copy link
Member Author

kentonv commented Mar 1, 2017

Garply, retest this please.

@kentonv kentonv merged commit d534fcb into master Mar 1, 2017
@kentonv kentonv deleted the powerbox-external-http-api branch March 1, 2017 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants