blob: df60eee6acada68c76bbdfd0d37e48059db1201a [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.android.inspectors;
import android.util.Base64;
import android.util.Log;
import com.google.common.collect.ImmutableList;
import org.joda.time.DateTime;
import org.joda.time.ReadableDuration;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPublicKey;
import java.util.List;
import io.v.android.v23.V;
import io.v.v23.context.VContext;
import io.v.v23.naming.Endpoint;
import io.v.v23.rpc.ListenSpec;
import io.v.v23.security.Blessings;
import io.v.v23.security.Caveat;
import io.v.v23.security.VPrincipal;
import io.v.v23.security.VSecurity;
import io.v.v23.security.access.Tag;
import io.v.v23.verror.VException;
import io.v.v23.vom.VomUtil;
/**
* A RemoteInspectors object creates and manages a secure network server that allows authorized
* remote users to inspect the (Vanadium-specific) state of the application such as statistics,
* and log files.
*
* <p>When a remote user is invited via the {@link #invite(String, ReadableDuration)} method, a
* principal and blessing is generated that is suitable for use by the
* <a href="https://godoc.org/v.io/x/ref/services/debug/debug">{@code debug browse}</a> command.
* The blessing allows only for debug access and thus does not grant the remote user any access to
* change application state (techinically, does not allow them to invoke any method not tagged with
* {@link io.v.v23.security.access.Constants#DEBUG}).
*/
public class RemoteInspectors {
private static final String TAG = "RemoteInspectors";
private static final int BASE64_FLAGS = Base64.URL_SAFE | Base64.NO_WRAP;
private VContext mCtx;
private String mMountedName;
/**
* Creates a secure network server to expose application state to invited remote users.
*
* @param ctx the Vanadium context of the application whose state is to be exposed
* @throws VException
*/
public RemoteInspectors(VContext ctx) throws VException {
mCtx = ctx.withCancel();
try {
mMountedName = createMountedName(ctx);
} catch (NoSuchAlgorithmException e) {
throw new VException("Unable to create mounted name:" + e);
}
Log.i(TAG, "Application state should be inspectable via: " + mMountedName);
mCtx = V.withNewServer(
V.withListenSpec(mCtx, new ListenSpec("tcp", ":0").withProxy("proxy")),
mMountedName,
new InspectedServer() {},
VSecurity.newDefaultAuthorizer());
}
/**
* Invite a remote user to inspect state of the application.
*
* @param invitee the name to refer to the remote user as (typically an email address)
* @param duration time after which inspection privileges expire
*
* @return A textual description of how the remote user can access state of the running
* application.
*/
public synchronized String invite(String invitee, ReadableDuration duration)
throws VException {
if (mCtx == null) {
throw new VException("RemoteInspectors.stop already called");
}
// In sync with the "debug delegate" command in
// v.io/x/ref/services/debug/debug/browse.go
List<Tag> tags = ImmutableList.of(
io.v.v23.security.access.Constants.DEBUG,
io.v.v23.security.access.Constants.RESOLVE);
Caveat debugOnly = VSecurity.newCaveat(
io.v.v23.security.access.Constants.ACCESS_TAG_CAVEAT, tags);
Caveat expiration = VSecurity.newExpiryCaveat(DateTime.now().plus(duration));
String privateKey;
VPrincipal delegate;
try {
KeyPairGenerator e = KeyPairGenerator.getInstance("EC");
e.initialize(256);
KeyPair keyPair = e.generateKeyPair();
privateKey = Base64.encodeToString(keyPair.getPrivate().getEncoded(), BASE64_FLAGS);
delegate = VSecurity.newPrincipal(
VSecurity.newSigner(keyPair.getPrivate(), (ECPublicKey) keyPair.getPublic()));
} catch (NoSuchAlgorithmException e) {
throw new VException("Could not mint private key: " + e.getMessage());
}
VPrincipal me = V.getPrincipal(mCtx);
Blessings b = me.bless(
delegate.publicKey(),
me.blessingStore().defaultBlessings(),
"debugger" + io.v.v23.security.Constants.CHAIN_SEPARATOR + invitee,
debugOnly,
expiration);
StringBuilder builder = new StringBuilder()
.append("Please inspect my application using:")
.append('\n')
.append('\n')
.append("debug browse")
.append(" --key=").append(privateKey)
.append(" --blessings=").append(Base64.encodeToString(
VomUtil.encode(b, b.getClass()), BASE64_FLAGS))
.append(" ");
if (mMountedName.length() > 0) {
builder.append(mMountedName).append(" ");
}
for (Endpoint ep : V.getServer(mCtx).getStatus().getEndpoints()) {
builder.append(ep.name()).append(" ");
}
return builder.toString();
}
private static String createMountedName(VContext ctx) throws NoSuchAlgorithmException {
// TODO(ashankar): Use a conventions library here, something like
// https://godoc.org/v.io/v23/conventions
// What follows below is NOT worthy of any form of emulation!
//
// The intent is to create a unique name for this application that it will have
// permission to mount under. The place it has permissions to mount under depends on
// the kind of blessing: <idp>:u:<email>:... should have permissions under user/<email>/...
// while <idp>:o:<appid>:<email> should ultimately go under apps/<appid>/users/<email>/...
// but for now will be placed under tmp/debug/<something>.
//
// The hash of the public key of the principal is used to uniquely identify this particular
// instance of the application.
VPrincipal p = V.getPrincipal(ctx);
String[] myNames = VSecurity.getBlessingNames(p, p.blessingStore().defaultBlessings());
String uid = Base64.encodeToString(
MessageDigest.getInstance("SHA-256").digest(p.publicKey().getEncoded()),
BASE64_FLAGS);
for (String n : myNames) {
// Does this follow the "idp:u:<username>" path?
String[] parts = n.split(io.v.v23.security.Constants.CHAIN_SEPARATOR);
if (parts.length >=3 && parts[1].equals("u")) {
return "users/" + parts[2] + "debug/" + uid;
}
if (parts.length >=4 && parts[1].equals("o")) {
// TODO(ashankar,ribrdb): Replace this with whatever convention we end up with
// when it comes to application blessings.
return "tmp/debug/" + uid;
}
}
return "";
}
}