Two-Factor Authentication the right way

About two-factor authentication:

You may or may not use two-factor authentication (2FA) on your important online identities (like Google, Facebook, Twitter, or any other ‘personal’ account).

Photo showing two-factor authentication

So, whenever you log into one of those accounts you are prompted for two things next to your username, namely your password and two-factor token.

In Theory:

Why does this token create an extra benefit? Let’s look at some of the different flavours of authentication:

  • Something your know (like a password). This is one of the most common means of online authentication. You fill in a piece of text only known by you, so the system can verify that you are who you say you are.
  • Something you have (like a smartcard, also called a Security Token). This form is used a lot more in offline environments. If you take a bank for example, the S_omething you have_ can be your ATM-card. And they combine it with Something you know, namely your PIN.
  • Something you are (like a fingerprint). This form is sometimes found on certain laptops, and can even be used to configure two-factor login with PAM under linux.

So, two-factor authentication is a combination of above named flavours. Because it’s a combination, it’s sometimes called ‘multi-factor authentication’.

In practice:

Previous situation:

I used to have two-factor setup only on a select few important accounts for me. Although it worked out, I was kind of scared for losing my phone, since losing it, means losing access to the generated that creates the OTP (One Time Password) tokens.

When you set your account up, you usually get the option of generating a few ‘scratch-codes’. These codes are not bound by time, but can be used in case of an emergency. However, I needed a place to store the codes.

I used to store them in my password manager, but I wasn’t too happy about that, since it would break the multiple factors of something I know, and something I have stored together.

Current situation:

You may have read my blogpost about the YubiKey Neo which I use for PGP subkeys. One other thing the Neo can do, is using NFC. If you are familiar with Google Authenticator on your mobile device, Yubico offers a slightly patched alternative called Yubico Authenticator. It does things very similar, altough it requires NFC of the YubiKey to show the OTPs. One benefit of this is that I can use it on any device which has the Yubico Authenticator installed.

This photo is taken from the Yubico blog https://www.yubico.com/2013/09/yubikey-neo-oath-applet/

This photo belongs to Yubico

The same problem I had in the past still stands unfortunately, what if I lose my YubiKey? Well, for PGP, I can create new subkeys, but for OTPs, I can’t, since I don’t know the secret.

I’ve found a trick online which is so simple that I feel stupid not thinking about it before, whenever you set up two-factor authentication, you usually get a nice QR-code that you would scan to transfer the secret onto your device. You then enter one of the OTPs to confirm the generator works properly.

I have an old iPhone 4 laying around which wasn’t used anymore since it’s too slow now-a-days, however I still have Google Authenticator installed. I setup all of my codes again for the YubiKey, and at the same time scanned the QR-codes with my iPhone, and verified that both generate the same keys before confirming the new secret. That way I always have a ‘backup’ of my secrets.

I store this phone together with my USB-stick containing the PGP master-key, and only use it in emergencies.

Conclusion:

I now have a separation of something I know and something I have, while avoiding the risk of losing the something I have. Since I have a backup of on my old iPhone. On the other hand, I don’t even have to keep my phone with me, since the YubiKey works universally on any device that has the Yubico Authenticator.

If you have any questions or feedback, I’d love to hear from you in the comments below. Please let me know if you have made similar setups :)

PS. I am in no way affiliated with Yubico, I’m just a happy customer! :)

Tags: 2FA, Two-factor, YubiKey

YubiKey Neo with PGP subkeys

I have been using PGP keys for some time, using them to sign and/or encrypt my e-mails (I use Mozilla Thunderbird with the Enigmail plugin for this).

I’ve been wanting to create a setup where I use these keys for more then just e-mail, for encrypting files or as SSH authentication for example. This also includes the ‘pass‘ tool, which utilizes a combination of git and pgp as a password manager in a powerful way.

As an owner of a YubiKey Standard ($25 and only for OTP purposes), I’ve known about the more expensive models (like the $50 YubiKey Neo), as I know of the existance of OpenPGP smartcards in the world. An important difference is, quite a few smartcards allow for only 1024bits keys, while the YubiKey allows 2048bit keys.

I went for the YubiKey Neo, got it in the mail, and started playing with it. I thought, might as well make a tutorial blogpost over this to help anyone who is considering such a setup.

First off, you need GnuPG installed, which is available in package-managers among Unix-like distro’s. I am demonstrating this on OSX, but should work fairly similar on other operating systems.

$ brew install gpg2

When first starting gpg, it will generate two things in your $GNUPGHOME directory (by default ~/.gnupg)

gpg: keyring `/Users/myusername/.gnupg/secring.gpg’ created

gpg: keyring `/Users/myusername/.gnupg/pubring.gpg’ created

gpg: /Users/myusername/.gnupg/trustdb.gpg: trustdb created

These files serve the basis of all keys GnuPG will deal with from this point on.

When first inserting the YubiKey and listing information from GnuPG, it will look something like this

$ gpg –card-status

Application ID …: D2760001XXXX000006036XXXXX0000

Version ……….: 2.0

Manufacturer …..: Yubico

Serial number ….: 036XXXXX

Name of cardholder: [not set]

Language prefs …: [not set]

Sex …………..: unspecified

URL of public key : [not set]

Login data …….: [not set]

Signature PIN ….: forced

Key attributes …: 2048R 2048R 2048R

Max. PIN lengths .: 127 127 127

PIN retry counter : 3 3 3

Signature counter : 0

Signature key ….: [none]

Encryption key….: [none]

Authentication key: [none]

General key info..: [none]

Ofcourse, I’ve censored a few things here and there, but note the highlighted information. I can put three keys of 2048bit RSA on this device. Why are there three? And how do they differ?

Traditionally you can generate a PGP key and use it for whatever you like, signing, encrypting, authentication. Doesn’t really matter. Up until the point that your key may be compromised.

For prevent this, we are going to create a master key which can sign other keys, and three subkeys for Signing, Encryption and Authentication. The three subkeys will be integrated into the YubiKey, while the master key will live on an USB-stick stashed in a secret place.

To generate the master key, I’ve taken a bootable live debian image with no network attached, and mounted another USB-stick which I pointed out as the GNUPGHOME directory.

$ export GNUPGHOME=/media/user/USBSTICK/gnupghome

$ mkdir $GNUPGHOME

To generate the master key:

$ gpg –expert –gen-key

gpg (GnuPG/MacGPG2) 2.0.27; Copyright (C) 2015 Free Software Foundation, Inc.

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:

(1) RSA and RSA (default)

(2) DSA and Elgamal

(3) DSA (sign only)

(4) RSA (sign only)

(7) DSA (set your own capabilities)

(8) RSA (set your own capabilities)

Your selection? 8

Possible actions for a RSA key: Sign Certify Encrypt Authenticate

Current allowed actions: Sign Certify Encrypt

(S) Toggle the sign capability

(E) Toggle the encrypt capability

(A) Toggle the authenticate capability

(Q) Finished

Your selection? S

Possible actions for a RSA key: Sign Certify Encrypt Authenticate

Current allowed actions: Certify Encrypt

(S) Toggle the sign capability

(E) Toggle the encrypt capability

(A) Toggle the authenticate capability

(Q) Finished

Your selection? E

Possible actions for a RSA key: Sign Certify Encrypt Authenticate

Current allowed actions: Certify

(S) Toggle the sign capability

(E) Toggle the encrypt capability

(A) Toggle the authenticate capability

(Q) Finished

Your selection? Q

RSA keys may be between 1024 and 4096 bits long.

What keysize do you want? (2048) 4096

Requested keysize is 4096 bits

Please specify how long the key should be valid.

0 = key does not expire

= key expires in n days

w = key expires in n weeks

m = key expires in n months

y = key expires in n years

Key is valid for? (0) ****

Key does not expire at all

Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Dennis de Greef

Email address: dennis@example.com

Comment:

You selected this USER-ID:

“Dennis de Greef dennis@example.com

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O

You need a Passphrase to protect your secret key.

We need to generate a lot of random bytes. It is a good idea to perform

some other action (type on the keyboard, move the mouse, utilize the

disks) during the prime generation; this gives the random number

generator a better chance to gain enough entropy.

gpg: key AAB98B2F marked as ultimately trusted

public and secret key created and signed.

gpg: checking the trustdb

gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model

gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u

pub 4096R/AAB98B2F 2015-07-26

Key fingerprint = 69B2 F1E7 3A1F C011 D81C 802E 732D F32A AAB9 8B2F

uid [ultimate] Dennis de Greef dennis@example.com

So what we just did, was create an 4096bits RSA key with the only capability of signing other keys (we switched off Signing and Encryption). We set the expire date to never, since this key will forever be secret (stashed away somewhere). And if the key would somehow be compromised, we can use a revocation certificate (I’ll come back to that later).

It also asked for a passphrase, be sure to put in a strong password in here.

NOTE: Be sure to put in your valid e-mail address. If you want more e-mail addresses known, you can add them with adduid in the GPG edit-key mode.

Before proceeding, we want to create a backup of our master key to a plaintext format

$ gpg -a –export-secret-keys AAB98B2F > $GNUPGHOME/../masterkey.txt

$ cat $GNUPGHOME/../masterkey.txt

—–BEGIN PGP PRIVATE KEY BLOCK—–

Version: GnuPG/MacGPG2 v2

lQc+BFW0xnwBEADZ3q/nBuybiVZK9kbmoH2q3nccds0bS7l2iW4r7aT4Wm16z8RV

[…]

So, next we need to generate some subkeys to be able to perform the other capabilities.

$ gpg –expert –edit-key AAB98B2F

pub 4096R/AAB98B2F created: 2015-07-26 expires: never usage: C

trust: ultimate validity: ultimate

[ultimate] (1). Dennis de Greef dennis@example.com

gpg> addkey

Key is protected.

You need a passphrase to unlock the secret key for

user: “Dennis de Greef dennis@example.com

4096-bit RSA key, ID AAB98B2F, created 2015-07-26

Please select what kind of key you want:

(3) DSA (sign only)

(4) RSA (sign only)

(5) Elgamal (encrypt only)

(6) RSA (encrypt only)

(7) DSA (set your own capabilities)

(8) RSA (set your own capabilities)

Your selection? 8

Possible actions for a RSA key: Sign Encrypt Authenticate

Current allowed actions: Sign Encrypt

(S) Toggle the sign capability

(E) Toggle the encrypt capability

(A) Toggle the authenticate capability

(Q) Finished

Your selection? E

Possible actions for a RSA key: Sign Encrypt Authenticate

Current allowed actions: Sign

(S) Toggle the sign capability

(E) Toggle the encrypt capability

(A) Toggle the authenticate capability

(Q) Finished

Your selection? Q

RSA keys may be between 1024 and 4096 bits long.

What keysize do you want? (2048) 2048

Requested keysize is 2048 bits

Please specify how long the key should be valid.

0 = key does not expire

= key expires in n days

w = key expires in n weeks

m = key expires in n months

y = key expires in n years

Key is valid for? (0) 5y

Key expires at Fri Jul 24 13:45:33 2020 CEST

Is this correct? (y/N) y

Really create? (y/N) y

We need to generate a lot of random bytes. It is a good idea to perform

some other action (type on the keyboard, move the mouse, utilize the

disks) during the prime generation; this gives the random number

generator a better chance to gain enough entropy.

pub 4096R/AAB98B2F created: 2015-07-26 expires: never usage: C

trust: ultimate validity: ultimate

sub 2048R/A1EFF93C created: 2015-07-26 expires: 2020-07-24 usage: S

[ultimate] (1). Dennis de Greef dennis@example.com

gpg>

We now have created a subkey for signing. Repeat this process for Encryption and Authentication, and we have a master key with 3 subkeys for different capabilities. Type ‘save‘ to save your changes.

pub 4096R/AAB98B2F created: 2015-07-26 expires: never usage: C

trust: ultimate validity: ultimate

sub 2048R/A1EFF93C created: 2015-07-26 expires: 2020-07-24 usage: S

sub 2048R/4A71FF50 created: 2015-07-26 expires: 2020-07-24 usage: E

sub 2048R/77409C9F created: 2015-07-26 expires: 2020-07-24 usage: A

[ultimate] (1). Dennis de Greef dennis@example.com

It is good practice to generate a revocation certificate in case you lose your key. Store this in a safe place, possibly printed out on paper.

$ gpg –output $GNUPGHOME/../revocation-certificate.txt –gen-revoke AAB98B2F

sec 4096R/AAB98B2F 2015-07-26 Dennis de Greef dennis@example.com

Create a revocation certificate for this key? (y/N) y

Please select the reason for the revocation:

0 = No reason specified

1 = Key has been compromised

2 = Key is superseded

3 = Key is no longer used

Q = Cancel

(Probably you want to select 1 here)

Your decision? 1

Enter an optional description; end it with an empty line:

This revocation certificate is generated during key creation and should be used in emergencies only

>

Reason for revocation: Key has been compromised

This revocation certificate is generated during key creation and should be used in emergencies only

Is this okay? (y/N) y

You need a passphrase to unlock the secret key for

user: “Dennis de Greef dennis@example.com

4096-bit RSA key, ID AAB98B2F, created 2015-07-26

ASCII armored output forced.

Revocation certificate created.

Please move it to a medium which you can hide away; if Mallory gets

access to this certificate he can use it to make your key unusable.

It is smart to print this certificate and store it away, just in case

your media become unreadable. But have some caution: The print system of

your machine might store the data and make it available to others!

Like the description says, store this away somewhere safe, and only use it when your master keys needs to be revoked.

Now we want to make a few backups in plaintext of our master key including the subkeys

$ gpg -a –export-secret-keys AAB98B2F > $GNUPGHOME/../mastersubkeys.txt

$ cat $GNUPGHOME/../mastersubkeys.txt

—–BEGIN PGP PRIVATE KEY BLOCK—–

Version: GnuPG/MacGPG2 v2

lQc+BFW0xnwBEADZ3q/nBuybiVZK9kbmoH2q3nccds0bS7l2iW4r7aT4Wm16z8RV

[…]

And for the subkeys

$ gpg -a –export-secret-subkeys AAB98B2F > $GNUPGHOME/../subkeys.txt

$ cat $GNUPGHOME/../subkeys.txt

—–BEGIN PGP PRIVATE KEY BLOCK—–

Version: GnuPG/MacGPG2 v2

lQIVBFW0xnwBEADZ3q/nBuybiVZK9kbmoH2q3nccds0bS7l2iW4r7aT4Wm16z8RV

Now we can setup the YubiKey to contain personal information and our subkeys. Make sure the device is in OTP/CCID or CCID mode.

$ brew install yubikey-personalization

$ ykpersonalize -m82

Now edit the card to personalise it.

$ gpg –card-edit

gpg/card> admin

Admin commands are allowed

gpg/card> passwd

gpg: OpenPGP card no. D2760001XXXX000006036XXXXX0000 detected

1 – change PIN

2 – unblock PIN

3 – change Admin PIN

4 – set the Reset Code

Q – quit

Your selection? 3

PIN changed.

1 – change PIN

2 – unblock PIN

3 – change Admin PIN

4 – set the Reset Code

Q – quit

Your selection? 1

PIN changed.

1 – change PIN

2 – unblock PIN

3 – change Admin PIN

4 – set the Reset Code

Q – quit

Your selection? q

gpg/card> name

Cardholder’s surname: De Greef

Cardholder’s given name: Dennis

gpg/card> lang

Language preferences: en

gpg/card> url

URL to retrieve public key: https://dennisdegreef.net/aab98b2f.txt

gpg/card> sex

Sex ((M)ale, (F)emale or space): m

gpg/card> login

Login data (account name): dennisdegreef

gpg/card> list

Application ID …: D2760001XXXX000006036XXXXX0000

Version ……….: 2.0

Manufacturer …..: Yubico

Serial number ….: 036XXXXX

Name of cardholder: Dennis De Greef

Language prefs …: en

Sex …………..: male

URL of public key : https://dennisdegreef.net/aab98b2f.txt

Login data …….: dennisdegreef

Signature PIN ….: forced

Key attributes …: 2048R 2048R 2048R

Max. PIN lengths .: 127 127 127

PIN retry counter : 3 3 3

Signature counter : 0

Signature key ….: [none]

Encryption key….: [none]

Authentication key: [none]

General key info..: [none]

gpg/card> quit

Next, we want to embed the subkeys into the card.

$ gpg –edit-key AAB98B2F

gpg (GnuPG/MacGPG2) 2.0.27; Copyright (C) 2015 Free Software Foundation, Inc.

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

pub 4096R/AAB98B2F created: 2015-07-26 expires: never usage: C

trust: ultimate validity: ultimate

sub 2048R/A1EFF93C created: 2015-07-26 expires: 2020-07-24 usage: S

sub 2048R/4A71FF50 created: 2015-07-26 expires: 2020-07-24 usage: E

sub 2048R/77409C9F created: 2015-07-26 expires: 2020-07-24 usage: A

[ultimate] (1). Dennis de Greef dennis@example.com

gpg> toggle

sec 4096R/AAB98B2F created: 2015-07-26 expires: never

ssb 2048R/A1EFF93C created: 2015-07-26 expires: never

ssb 2048R/4A71FF50 created: 2015-07-26 expires: never

ssb 2048R/77409C9F created: 2015-07-26 expires: never

(1) Dennis de Greef dennis@example.com

gpg> key 1

sec 4096R/AAB98B2F created: 2015-07-26 expires: never

ssb* 2048R/A1EFF93C created: 2015-07-26 expires: never

ssb 2048R/4A71FF50 created: 2015-07-26 expires: never

ssb 2048R/77409C9F created: 2015-07-26 expires: never

(1) Dennis de Greef dennis@example.com

gpg> keytocard

Signature key ….: [none]

Encryption key….: [none]

Authentication key: [none]

Please select where to store the key:

(1) Signature key

(3) Authentication key

Your selection? 1

You need a passphrase to unlock the secret key for

user: “Dennis de Greef dennis@example.com

4096-bit RSA key, ID AAB98B2F, created 2015-07-26

sec 4096R/AAB98B2F created: 2015-07-26 expires: never

ssb* 2048R/A1EFF93C created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb 2048R/4A71FF50 created: 2015-07-26 expires: never

ssb 2048R/77409C9F created: 2015-07-26 expires: never

(1) Dennis de Greef dennis@example.com

gpg> key 1

sec 4096R/AAB98B2F created: 2015-07-26 expires: never

ssb 2048R/A1EFF93C created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb 2048R/4A71FF50 created: 2015-07-26 expires: never

ssb 2048R/77409C9F created: 2015-07-26 expires: never

(1) Dennis de Greef dennis@example.com

gpg> key 2

sec 4096R/AAB98B2F created: 2015-07-26 expires: never

ssb 2048R/A1EFF93C created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb* 2048R/4A71FF50 created: 2015-07-26 expires: never

ssb 2048R/77409C9F created: 2015-07-26 expires: never

(1) Dennis de Greef dennis@example.com

gpg> keytocard

Signature key ….: EFA4 D5F7 95E0 4392 E42A 59CE DFE1 6372 72D2 245B

Encryption key….: [none]

Authentication key: [none]

Please select where to store the key:

(2) Encryption key

Your selection? 2

You need a passphrase to unlock the secret key for

user: “Dennis de Greef dennis@example.com

4096-bit RSA key, ID AAB98B2F, created 2015-07-26

sec 4096R/AAB98B2F created: 2015-07-26 expires: never

ssb 2048R/A1EFF93C created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb* 2048R/4A71FF50 created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb 2048R/77409C9F created: 2015-07-26 expires: never

(1) Dennis de Greef dennis@example.com

gpg> key 2

sec 4096R/AAB98B2F created: 2015-07-26 expires: never

ssb 2048R/A1EFF93C created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb 2048R/4A71FF50 created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb 2048R/77409C9F created: 2015-07-26 expires: never

(1) Dennis de Greef dennis@example.com

gpg> key 3

sec 4096R/AAB98B2F created: 2015-07-26 expires: never

ssb 2048R/A1EFF93C created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb 2048R/4A71FF50 created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb* 2048R/77409C9F created: 2015-07-26 expires: never

(1) Dennis de Greef dennis@example.com

gpg> keytocard

Signature key ….: EFA4 D5F7 95E0 4392 E42A 59CE DFE1 6372 72D2 245B

Encryption key….: A22D 5135 CCFC 947C 8925 9E77 A11F 46D4 35F4 CC42

Authentication key: [none]

Please select where to store the key:

(3) Authentication key

Your selection? 3

You need a passphrase to unlock the secret key for

user: “Dennis de Greef dennis@example.com

4096-bit RSA key, ID AAB98B2F, created 2015-07-26

sec 4096R/AAB98B2F created: 2015-07-26 expires: never

ssb 2048R/A1EFF93C created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb 2048R/4A71FF50 created: 2015-07-26 expires: never

card-no: 0123 00000042

ssb* 2048R/77409C9F created: 2015-07-26 expires: never

card-no: 0123 00000042

(1) Dennis de Greef dennis@example.com

gpg> save

$

NOTE: This article is a work in progress, we still need to send the keys to the keyserver, so everyone will know the public keys of our generated master key (which will validate all the subkeys as well).

Developing inside Vagrant with Docker-Compose

I’ve been following the Docker movement for quite some time now, and have seen examples of Docker-Compose passing along, so I thought, why not try and setup a nice development environment where I would develop an application that uses Docker-Compose.

For all you tl;dr’s, perform:

git clone --recursive https://github.com/dennisdegreef/blog-docker-compose-infrastructure 
cd blog-docker-compose-infrastructure
vagrant up

DISCLAIMER: This example is just to setup a docker-compose setup for development, and is currently using Symfony’s build-in webserver, which isn’t recommended for production! This is also just a try-out.

For this example, I am creating a simple Symfony application, which needs Redis instance.

First, we need a vagrant plugin to be able to use docker-compose as a provisioner

# vagrant plugin install vagrant-docker-compose

Next, we need to set up a Vagrantfile inside our infrastructure repository, and configure it using the docker provisioner. The provisioner itself will install Docker inside the vagrantbox and build the base image, on which we ‘base’ our own Dockerfile

# Vagrantfile
config.vm.provision :docker do |d|
    d.build_image "/vagrant/docker/base", args: "-t link0/base"
end

Now, we want to create an docker-compose.yml file to provide the structure and requirements of our application. This setup explains docker-compose to build the ‘frontend’ image, start it with port 80 exposed, and the current directory linked as /srv

# docker-compose.yml
frontend:
  build: link0/example-frontend
  ports:
    - "80:8000"
  volumes:
    - .:/srv
  links:
    - redis
redis:
  image: redis

Now that we have the docker-compose.yml, we can instruct Vagrant to use that structure upon provisioning.

config.vm.provision :docker_compose, yml: "/vagrant/docker-compose.yml", run: "always"

But before we can run `vagrant up`, we need to add our frontend application into the infrastructure project. I like to use git submodules for this.

git submodule add git@github.com:dennisdegreef/blog-docker-compose-frontend.git frontend

Now we can try it out, see if it works :)

vagrant up

It still breaks with the following error message

Building frontend...
Cannot locate specified Dockerfile: Dockerfile

This is because our frontend application has no Dockerfile to describe how it should be running. Let’s add one.

# blog-docker-compose-frontend/Dockerfile
FROM link0/base:latest
MAINTAINER Dennis de Greef <github@link0.net>

COPY start.sh /start.sh

CMD [ "/start.sh" ]
#!/usr/bin/env bash
# blog-docker-compose-frontend/start.sh
cd /srv/frontend;
/srv/frontend/composer.phar install && \
/srv/frontend/app/console server:run 0.0.0.0:8000

And when you now run

vagrant up

And point your browser to http://192.168.42.10/ You should be able to see ‘Hello Docker-Compose’.

You can check (tail) the logs using the following command

vagrant ssh -c 'docker logs -f vagrant_frontend_1'

If some things are unclear, please let me know in the comments.

Tags: docker