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

Dockerise CLI and HTTP server #11

Merged
merged 6 commits into from
Dec 7, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/push_image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: push_image

on:
push:
branches:
- main
release:
types: [published, created, edited]

jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout DIDKit repository
uses: actions/checkout@v2
with:
path: didkit

- name: Checkout SSI library
uses: actions/checkout@v2
with:
repository: spruceid/ssi
token: ${{ secrets.GH_ACCESS_TOKEN_CEL }}
path: didkit/ssi

- name: Build and push CLI
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: spruceid/didkit-cli
username: ${{ github.actor }}
password: ${{ secrets.SBIHEL_GH_PACKAGE_PUSH_TOKEN }}
registry: ghcr.io
dockerfile: Dockerfile-cli
workdir: didkit

- name: Build and push HTTP
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: spruceid/didkit-http
username: ${{ github.actor }}
password: ${{ secrets.SBIHEL_GH_PACKAGE_PUSH_TOKEN }}
registry: ghcr.io
dockerfile: Dockerfile-http
workdir: didkit
14 changes: 14 additions & 0 deletions Dockerfile-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM ekidd/rust-musl-builder as builder

ADD --chown=rust:rust . /didkit
ADD --chown=rust:rust ./ssi /ssi
WORKDIR /didkit/cli

ENV CARGO_NET_GIT_FETCH_WITH_CLI=true
RUN cargo build --release

FROM alpine
COPY --from=builder /didkit/target/x86_64-unknown-linux-musl/release/didkit didkit
ENTRYPOINT ["./didkit"]


13 changes: 13 additions & 0 deletions Dockerfile-http
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM ekidd/rust-musl-builder as builder

ADD --chown=rust:rust . /didkit
ADD --chown=rust:rust ./ssi /ssi
WORKDIR /didkit/http

ENV CARGO_NET_GIT_FETCH_WITH_CLI=true
RUN cargo build --release

FROM alpine
COPY --from=builder /didkit/target/x86_64-unknown-linux-musl/release/didkit-http /usr/local/bin/

ENTRYPOINT ["didkit-http"]
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

## Install

### Container

Both the CLI and HTTP server are containerised and available under
`ghcr.io/spruceid/didkit-(cli|http)`.

The image are private for now, so a [Personal Access Token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token)
is required. Once created you can login like so:
```bash
$ docker login ghcr.io -u USERNAME --password-stdin
```

You can use the images like CLIs:
```bash
$ docker run ghcr.io/spruceid/didkit-cli:latest --help
$ docker run --init -p 8080 ghcr.io/spruceid/didkit-http:latest --port 8080
```

> You can pass JWKs either by sharing a volume with `docker run --volume`, or by passing the JWK directly with `docker run -e JWK=$MY_JWK` or `docker run didkit-http --jwk $MY_JWK`.

### Manual

DIDKit is written in [Rust][]. To get Rust, you can use [Rustup][].

We depend on some Rust nightly features. When installing with Rustup, pick the nightly release channel. Or run `rustup default nightly` to switch to it.
Expand Down
8 changes: 5 additions & 3 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Given a Ed25519 [JWK][], output the corresponding [did:key][] [verificationMetho

#### Options

- `-k, --key <file>` (required) - Name of JWK file
- `-k, --key-path <file>` (required) - Filename of JWK file
- `-j, --jwk <jwk>` (required) - JWK.
Copy link
Contributor

Choose a reason for hiding this comment

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

how it's written now implies that both are required, perhaps we could note the either-or-ness as we do below?


### `didkit vc-issue-credential`

Expand All @@ -47,13 +48,14 @@ The proof type is set automatically based on the key file provided. JWK paramete

#### Options

Options besides `--key` correspond to linked data [proof options][] as specified in [ld-proofs][] and [vc-http-api][].
Options besides `--key-path` correspond to linked data [proof options][] as specified in [ld-proofs][] and [vc-http-api][].

- `-C, --challenge <challenge>` - [challenge][] property of the proof
- `-c, --created <created>` - [created][] property of the proof. ISO8601 datetime. Defaults to the current time.
time.
- `-d, --domain <domain>` - [domain][] property of the proof
- `-k, --key <key>` (required) - Filename of JWK for signing.
- `-k, --key-path <key>` (required, conflicts with jwk) - Filename of JWK for signing.
- `-j, --jwk <jwk>` (required, conflicts with key-path) - JWK for signing.
- `-p, --proof-purpose <proof-purpose>` [proofPurpose][] property of the proof.
- `-v, --verification-method <verification-method>` [verificationMethod][]
property of the proof. URI for proof verification information, e.g. a public key identifier.
Expand Down
95 changes: 58 additions & 37 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::io::{stdin, stdout, BufReader, BufWriter};
use std::path::PathBuf;

use chrono::prelude::*;
use structopt::StructOpt;
use structopt::{clap::ArgGroup, StructOpt};

use didkit::{
LinkedDataProofOptions, ProofPurpose, VerifiableCredential, VerifiablePresentation, JWK,
Expand All @@ -15,13 +15,13 @@ pub enum DIDKit {
GenerateEd25519Key,
/// Output a did:key DID for a JWK
KeyToDIDKey {
#[structopt(short, long, parse(from_os_str))]
key: PathBuf,
#[structopt(flatten)]
key: KeyArg,
},
/// Output a verificationMethod for a JWK
KeyToVerificationMethod {
#[structopt(short, long, parse(from_os_str))]
key: PathBuf,
#[structopt(flatten)]
key: KeyArg,
},

/*
Expand All @@ -44,8 +44,8 @@ pub enum DIDKit {
// VC Functionality
/// Issue Credential
VCIssueCredential {
#[structopt(short, long, parse(from_os_str))]
key: PathBuf,
#[structopt(flatten)]
key: KeyArg,
#[structopt(flatten)]
proof_options: ProofOptions,
},
Expand All @@ -56,8 +56,8 @@ pub enum DIDKit {
},
/// Issue Presentation
VCIssuePresentation {
#[structopt(short, long, parse(from_os_str))]
key: PathBuf,
#[structopt(flatten)]
key: KeyArg,
#[structopt(flatten)]
proof_options: ProofOptions,
},
Expand All @@ -84,18 +84,54 @@ pub enum DIDKit {

#[derive(StructOpt, Debug)]
pub struct ProofOptions {
#[structopt(short, long)]
#[structopt(env, short, long)]
pub verification_method: Option<String>,
#[structopt(short, long)]
#[structopt(env, short, long)]
pub proof_purpose: Option<ProofPurpose>,
#[structopt(short, long)]
#[structopt(env, short, long)]
pub created: Option<DateTime<Utc>>,
#[structopt(short = "C", long)]
#[structopt(env, short = "C", long)]
pub challenge: Option<String>,
#[structopt(short, long)]
#[structopt(env, short, long)]
pub domain: Option<String>,
}

#[derive(StructOpt, Debug)]
#[structopt(group = ArgGroup::with_name("key_group").required(true))]
pub struct KeyArg {
#[structopt(
env,
short,
long,
parse(from_os_str),
conflicts_with = "jwk",
group = "key_group"
)]
key_path: Option<PathBuf>,
#[structopt(
env,
short,
long,
parse(try_from_str = serde_json::from_str),
conflicts_with = "key_path",
group = "key_group"
)]
jwk: Option<JWK>,
}

impl KeyArg {
fn get_jwk(&self) -> JWK {
match &self.key_path {
Some(p) => {
let key_file = File::open(p).unwrap();
let key_reader = BufReader::new(key_file);
serde_json::from_reader(key_reader).unwrap()
}
None => self.jwk.clone().unwrap(),
}
}
}

impl From<ProofOptions> for LinkedDataProofOptions {
fn from(options: ProofOptions) -> LinkedDataProofOptions {
LinkedDataProofOptions {
Expand All @@ -109,13 +145,6 @@ impl From<ProofOptions> for LinkedDataProofOptions {
}
}

fn read_key(key_path: PathBuf) -> JWK {
let key_file = File::open(key_path).unwrap();
let key_reader = BufReader::new(key_file);
let key: JWK = serde_json::from_reader(key_reader).unwrap();
key
}

fn main() {
let opt = DIDKit::from_args();
match opt {
Expand All @@ -125,23 +154,18 @@ fn main() {
println!("{}", jwk_str);
}

DIDKit::KeyToDIDKey { key: key_path } => {
let key = read_key(key_path);
let did = key.to_did().unwrap();
DIDKit::KeyToDIDKey { key } => {
let did = key.get_jwk().to_did().unwrap();
println!("{}", did);
}

DIDKit::KeyToVerificationMethod { key: key_path } => {
let key = read_key(key_path);
let did = key.to_verification_method().unwrap();
DIDKit::KeyToVerificationMethod { key } => {
let did = key.get_jwk().to_verification_method().unwrap();
println!("{}", did);
}

DIDKit::VCIssueCredential {
key: key_path,
proof_options,
} => {
let key: JWK = read_key(key_path);
DIDKit::VCIssueCredential { key, proof_options } => {
let key: JWK = key.get_jwk();
let credential_reader = BufReader::new(stdin());
let mut credential: VerifiableCredential =
serde_json::from_reader(credential_reader).unwrap();
Expand All @@ -166,11 +190,8 @@ fn main() {
}
}

DIDKit::VCIssuePresentation {
key: key_path,
proof_options,
} => {
let key: JWK = read_key(key_path);
DIDKit::VCIssuePresentation { key, proof_options } => {
let key: JWK = key.get_jwk();
let presentation_reader = BufReader::new(stdin());
let mut presentation: VerifiablePresentation =
serde_json::from_reader(presentation_reader).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn generate_key() {
fn issue_verify_credential_presentation() {
// Get DID for key
let did_output = Command::new(BIN)
.args(&["key-to-did-key", "-k", "tests/ed25519-key.jwk"])
.args(&["key-to-did-key", "--key-path", "tests/ed25519-key.jwk"])
.stderr(Stdio::inherit())
.output()
.unwrap();
Expand Down
3 changes: 2 additions & 1 deletion http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ and runs until interrupted.
- `-p, --port <port>` - Port to listen on. Default is a random OS-chosen port.
- `-k, --key <key>` - Filename of a JWK to use for issuing credentials and
presentations.
- `-j, --jwk <jwk>` - JWK to use for issuing credentials and presentations.

#### Issuer keys

Provide issuer keys using the `-k`/`--key` option. If none are provided, issuance functionality will be unavailable. If one is provided, that one will be used to sign all credentials and presentations, regardless of the proof options in the issuance request. If more than one key is provided, the issuance request may identify which key to use for signing by its DID in the `verificationMethod` property of the proof options; if none is identified in that property, the first key is used.
Provide issuer keys using the `-k`/`--key-path` or `-j`/`--jwk` options. If none are provided, issuance functionality will be unavailable. If one is provided, that one will be used to sign all credentials and presentations, regardless of the proof options in the issuance request. If more than one key is provided, the issuance request may identify which key to use for signing by its DID in the `verificationMethod` property of the proof options; if none is identified in that property, the first key is used.

## Rust library

Expand Down
55 changes: 41 additions & 14 deletions http/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,59 @@ use didkit_http::Error;
#[derive(StructOpt, Debug)]
pub struct DIDKitHttpOpts {
/// Port to listen on
#[structopt(short, long)]
#[structopt(env, short, long)]
port: Option<u16>,
/// Hostname to listen on
#[structopt(short = "s", long)]
#[structopt(env, short = "s", long)]
host: Option<std::net::IpAddr>,
/// JWK to use for issuing
#[structopt(short, long, parse(from_os_str))]
key: Option<Vec<PathBuf>>,
#[structopt(flatten)]
key: KeyArg,
}

fn read_key(path: &PathBuf) -> JWK {
let file = File::open(path).unwrap();
let reader = BufReader::new(file);
serde_json::from_reader(reader).unwrap()
#[derive(StructOpt, Debug)]
pub struct KeyArg {
#[structopt(
env,
short,
long,
parse(from_os_str),
conflicts_with = "jwk",
group = "key_group"
)]
key_path: Option<Vec<PathBuf>>,
#[structopt(
env,
short,
long,
parse(try_from_str = serde_json::from_str),
conflicts_with = "key_path",
group = "key_group"
)]
jwk: Option<Vec<JWK>>,
}

impl KeyArg {
fn get_jwks(&self) -> Vec<JWK> {
match self.key_path.clone() {
Some(paths) => paths
.iter()
.map(|filename| {
let key_file = File::open(filename).unwrap();
let key_reader = BufReader::new(key_file);
serde_json::from_reader(key_reader).unwrap()
})
.collect(),
None => self.jwk.clone().unwrap_or_default(),
}
}
}

#[tokio::main]
async fn main() -> Result<(), Error> {
let opt = DIDKitHttpOpts::from_args();

let keys = opt
.key
.unwrap_or_default()
.iter()
.map(|filename| read_key(filename))
.collect();
let keys = opt.key.get_jwks();
let makesvc = DIDKitHTTPMakeSvc::new(keys);
let host = opt.host.unwrap_or([127, 0, 0, 1].into());
let addr = (host, opt.port.unwrap_or(0)).into();
Expand Down