Skip to content

Commit

Permalink
switch to azure queues and enable akr to use local ssh keys (#22)
Browse files Browse the repository at this point in the history
* switch to azure queues, enable local RSA, ECDSA and ED25519 keys with akr
  • Loading branch information
nikhilty committed Jun 8, 2022
1 parent 9babada commit 7d73416
Show file tree
Hide file tree
Showing 15 changed files with 1,659 additions and 269 deletions.
581 changes: 559 additions & 22 deletions Cargo.lock

Large diffs are not rendered by default.

66 changes: 46 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
</p>

# Akamai "Krypton" FIDO2 SSH Agent and CLI

The `akr` command line utility is Akamai's "Krypton" SSH Agent, the successor to [`kr`](https://github.com/kryptco/kr) which works exclusively with the [Akamai MFA Authenticator](https://mfa.akamai.com/app) for iOS and Android.
Akr enables your smart phone to become a "push-based" FIDO2 authenticator for SSH authentication.

`akr` enables SSH to authenticate with a FIDO2 key stored in the __Akamai MFA Authenticator app__
`akr` enables SSH to authenticate with a FIDO2 key stored in the **Akamai MFA Authenticator app**
([iOS](https://apps.apple.com/us/app/akamai-pushzero/id1503619894#?platform=iphone) +
[Android](https://play.google.com/store/apps/details?id=com.akamai.pushzero)).

Expand All @@ -18,71 +19,82 @@ not. If allowed, the phone simply sends the signature back to the agent. _Privat
> ⚠️ `akr` is currently in early-preview mode! Please contact us with any issues you find or feature suggestions.
# Getting Started

## First run

1. First, run `akr setup` to create configurations and start the agent
2. Next, pair your device: run `akr pair`
3. Scan the QR code with the [Akamai MFA app](https://mfa.akamai.com/app)
4. Run `akr generate --name mykey` to generate your first SSH key in Akamai MFA. This will output your SSH __public__ key.
4. Run `akr generate --name mykey` to generate your first SSH key in Akamai MFA. This will output your SSH **public** key.
5. Add your public key to a server or `github.com`


## Verify everything works

To verify whether your Akamai MFA FIDO2 key works, try the following:

```sh
$ ssh ssh.demo.krypt.co -p 5000
```

If everything works correctly, you should see something like this:

```sh
Hello John!

You have successfully authenticated to the Akamai MFA SSH FIDO2 test server!
You have successfully authenticated to the Akamai MFA SSH FIDO2 test server!
```

## Overview of Commands

Usage:
`akr [options] [command] [arguments]`

Options:
| Syntax | Description |
| - | - |

| Syntax | Description |
| ------------- | ---------------------------------------------- |
| -V, --version | Display the version number for the akr client. |
| -h, --help | Display usage information for akr client. |

| -h, --help | Display usage information for akr client. |

Commands:

| Command | Description | Example
| - | - | - |
| setup | Setup the background daemon and updates ssh configuration | `akr setup --ssh-config-path <ssh_config_file_path>`
| pair | Pair with your phone/tablet | `akr pair`
| generate | Generate a new SSH credential | `akr generate --name <ssh_credential_name>`
| unpair | Unpair from your phone/tablet | `akr unpair`
| load | Load public keys from the Akamai MFA app on your phone/tablet | `akr load`
| status | Get pairing info from your phone/tablet | `akr status`
| check | Health check of all the dep systems and system configs| `akr check`
| Command | Description | Example |
| -------- | ------------------------------------------------------------- | ---------------------------------------------------- |
| setup | Setup the background daemon and updates ssh configuration | `akr setup --ssh-config-path <ssh_config_file_path>` |
| pair | Pair with your phone/tablet | `akr pair` |
| generate | Generate a new SSH credential | `akr generate --name <ssh_credential_name>` |
| unpair | Unpair from your phone/tablet | `akr unpair` |
| load | Load public keys from the Akamai MFA app on your phone/tablet | `akr load` |
| status | Get pairing info from your phone/tablet | `akr status` |
| check | Health check of all the dep systems and system configs | `akr check` |

## Requirements
* macOS (10.15+) or Linux (64 Bit) (Debian, RHEL, and CentOS).
* OpenSSH Client and Server 8.2+

- macOS (10.15+) or Linux (64 Bit) (Debian, RHEL, and CentOS).
- OpenSSH Client and Server 8.2+
- pinentry

## Installation instructions

### macOS (brew)

```sh
brew install akamai/mfa/akr
brew install pinentry-mac
```

### Debian

```sh
curl -SsL https://akamai.github.io/akr-pkg/debian/KEY.gpg | sudo apt-key add -
sudo curl -SsL -o /etc/apt/sources.list.d/akr.list https://akamai.github.io/akr-pkg/debian/akr.list
sudo apt update
sudo apt install akr
sudo apt install pinentry-tty
```

### CentOS/RHEL

```sh
sudo vim /etc/yum.repos.d/akr.repo

Expand All @@ -92,34 +104,48 @@ baseurl=https://akamai.github.io/akr-pkg/rpm/
gpgcheck=0
enabled=1
```

```sh
sudo yum -y update
```

```sh
sudo yum -y install akr
sudo yum -y install pinentry-gtk
```

### Build from source

`akr` is built entirely with Rust. Ensure you have Rust installed (https://rustup.rs) and run `cargo build`.

## Notes on Configuration

Running `akr setup` updates your SSH config file and installs the `akr` ssh-agent as a background service on your system.
To see what `akr` configures, run `akr setup --print-only`.

The SSH config additions looks as follows:

```
# Begin Akamai MFA SSH Config
Host *
IdentityAgent /Users/<username>/.akr/akr-ssh-agent.sock
# End Akamai MFA SSH Config
```

This enables your native system SSH to communicate to the `akr` ssh-agent process over a unix socket.

## Notes

1. You can also use your existing local **RSA**, **ECDSA**, **ED25519** keys with akr as well. When you run `akr setup`, any exisiting local keys directly inside ~/.ssh folder gets loaded into the ssh-agent.
2. If you have an ECDSA key, please make sure the private key is in PEM format.

# Security Disclosure

For any security related questions, please contact our security team.
Please disclose any issues responsibly using our [Akamai Security GPG Public Key](https://www.akamai.com/us/en/multimedia/documents/infosec/akamai-security-general.pub)
and send communications to [security@akamai.com](mailto://security@akamai.com).

# License

Copyright (c) 2021, Akamai Technologies.
All rights reserved.
10 changes: 9 additions & 1 deletion crates/kr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ ansi_term = "0.12.1"
directories = "3.0.2"
dirs = "3.0.2"
serde-xml-rs = "0.5.0"

urlencoding = "2.1.0"
nix = "0.22.1"
openssl = "0.10"
bitflags = "1"
ecdsa = "0.13.4"
ring = "0.16.20"
untrusted = "0.9.0"
pem = "1.0.2"
osshkeys = "0.6.0"
[target.'cfg(target_os="macos")'.dependencies]
mac-notification-sys = "0.3.0"

Expand Down
23 changes: 16 additions & 7 deletions crates/kr/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ impl Client {
}

impl Client {
pub async fn create_queue(&mut self, uuid: Uuid) -> Result<(), Error> {
pub async fn create_queue(&self, uuid: Uuid) -> Result<(), Error> {
let _ = self.aws.create_queue(uuid).await;
let _ = self.azure.create_queue(uuid).await;
Ok(())
}

pub async fn send(
&mut self,
&self,
device_token: Option<String>,
queue_uuid: Uuid,
message: WireMessage,
Expand All @@ -55,7 +55,7 @@ impl Client {
Ok(())
}

pub async fn receive<T, F>(&mut self, queue_uuid: Uuid, on_messages: F) -> Result<T, Error>
pub async fn receive<T, F>(&self, queue_uuid: Uuid, on_messages: F) -> Result<T, Error>
where
F: Fn(&[WireMessage]) -> Result<Option<T>, Error> + Send + Copy,
{
Expand All @@ -68,7 +68,7 @@ impl Client {
Ok(res)
}

pub async fn send_request<R>(&mut self, request: RequestBody) -> Result<R, Error>
pub async fn send_request<R>(&self, request: RequestBody) -> Result<R, Error>
where
R: TryFrom<ResponseBody>,
Error: From<R::Error>,
Expand All @@ -94,7 +94,7 @@ impl Client {
Ok(std::convert::TryFrom::try_from(response.body)?)
}

pub async fn pz_health_check(&mut self) -> Result<QueueEvaluation, Error> {
pub async fn pz_health_check(&self) -> Result<QueueEvaluation, Error> {
match self.pzq.health_check().await {
Ok(_) => Ok(QueueEvaluation::Allow),
Err(_) => Ok(QueueEvaluation::Deny(QueueDenyError {
Expand All @@ -103,12 +103,21 @@ impl Client {
}
}

pub async fn aws_health_check(&mut self) -> Result<QueueEvaluation, Error> {
match self.pzq.health_check().await {
pub async fn aws_health_check(&self) -> Result<QueueEvaluation, Error> {
match self.aws.health_check().await {
Ok(_) => Ok(QueueEvaluation::Allow),
Err(_) => Ok(QueueEvaluation::Deny(QueueDenyError {
explanation: QueueDenyExplanation::AWSQueueDown,
})),
}
}

pub async fn azure_health_check(&self) -> Result<QueueEvaluation, Error> {
match self.azure.health_check().await {
Ok(_) => Ok(QueueEvaluation::Allow),
Err(_) => Ok(QueueEvaluation::Deny(QueueDenyError {
explanation: QueueDenyExplanation::AzureQueueDown,
})),
}
}
}
20 changes: 19 additions & 1 deletion crates/kr/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use openssl::error::ErrorStack;
use run_script::ScriptError;
use std::convert::Infallible;

Expand Down Expand Up @@ -95,6 +96,21 @@ pub enum Error {

#[error("Couldn't Parse SSH version: '{0}'")]
RunScriptError(#[from] ScriptError),

#[error("Unable to read azure token details")]
CannotReadAzureToken,

#[error("Sign flags contain incompatible bits")]
IllegalFlags,

#[error("Openssl operation failed: {0}")]
SslError(#[from] ErrorStack),

#[error("Invalid Bytes")]
FromUtf8Error(#[from] std::string::FromUtf8Error),

#[error("Unable to parse key")]
OsshKeysError(#[from] osshkeys::error::Error),
}

impl From<Infallible> for Error {
Expand All @@ -115,6 +131,7 @@ impl From<Error> for ssh_agent::error::Error {
pub enum QueueDenyExplanation {
PZQueueDown,
AWSQueueDown,
AzureQueueDown,
}

/// A richer error type for errors returned from the evaluation of user agents.
Expand All @@ -126,7 +143,8 @@ impl std::fmt::Display for QueueDenyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use QueueDenyExplanation::*;
let explanation = match self.explanation {
PZQueueDown => "Akamai MFA not reachable. Please make sure you can reach mfa.akamai.com",
PZQueueDown => "Akamai MFA not reachable. Please make sure you can reach mfa.akamai.com",
AzureQueueDown => "Azure is down or unreachable. Please make sure you can reach mfa.akamai.com & akamaikrypton.queue.core.windows.net",
AWSQueueDown => "AWS is down or unreachable. Please make sure you can reach sqs.us-east-1.amazonaws.com ",
};
f.write_str(&format!("{}", explanation))
Expand Down
19 changes: 16 additions & 3 deletions crates/kr/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::error::Error;
use askama::Template;
use nix::unistd::Uid;

#[derive(Debug, Clone)]
pub struct Daemon {
Expand Down Expand Up @@ -125,16 +126,28 @@ impl SystemdService {

let service_name = format!("{}.service", &self.bin_name);

let path = path.join(&service_name);
let path_to_write = path.clone().join(&service_name);
let contents = self.render()?;
std::fs::write(path, contents)?;
std::fs::write(path_to_write, contents)?;

let _ = std::process::Command::new("systemctl")
if Uid::effective().is_root() {

let _= std::process::Command::new("systemctl")
.arg("--now")
.arg("enable")
.arg(path.join(&service_name))
.output()?;
}

else {
let _ = std::process::Command::new("systemctl")
.arg("--user")
.arg("--now")
.arg("enable")
.arg(service_name)
.output()?;
}


Ok(())
}
Expand Down
Loading

0 comments on commit 7d73416

Please sign in to comment.