-
-
Notifications
You must be signed in to change notification settings - Fork 709
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
Conversation
There was a problem hiding this 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", |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/supprot/support/
}); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
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.
src/sandstorm/api-session-impl.capnp
Outdated
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
# This is used specifically with hack-session.capnp. |
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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. |
shell/packages/sandstorm-db/db.js
Outdated
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/needto/need to/
shell/packages/sandstorm-db/db.js
Outdated
@@ -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: |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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.)
…redentials in ApiToken.
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.
90cc456
to
60d3b87
Compare
Garply, retest this please. |
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 thecanonicalUrl
refers to a well-known OAuth API service (currently recognizes Github and some Google API hosts), an OAuth handshake will occur.