Skip to content

salrashid123/gcp-adc-pkcs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PKCS-11 Credential Source for Google Cloud SDK

Binary that just returns a Service Accounts access_token for use with GCP Credential Libraries where the key is accessed using PKCS-11.

While not running on a GCP platform like GCE, Cloud Run, GCF or GKE, Service Account authentication usually (with exception of workload federation) requires direct access to its RSA Private key..

You can see why here in the protocol itself in AIP-4111: Self-signed JWT with Scopes. Basically service account authentication involves locally signing a JWT and using that directly as an access_token.

What this repo offers is a way to generate the JWT while the RSA key is embedded on a PKCS-11 aware device like an HSM, TPM or even a Yubikey.

To use this, you must have a GCP service account key on the PKCS-11 device. There are three ways to do this:

  1. create an rsa key on the pkcs device, use that to create an x509 and upload that to GCP
  2. import a service account's private key into the pkcs-11 device.

This repo covers option 2 for simplicity.


I'm not going into the background of what PKCS-11 is but will state that its pretty particular in its setup.

For reference, some of the providers and enablement technology stack this repo covers

  • OpenSSL Provider

    • /usr/lib/x86_64-linux-gnu/engines-1.1/libpkcs11.so: OpenSSL Engine that allows dynamic PKCS11 providers
  • PKCS11 Modules

and other references/reps for PKCS-11

this repo is NOT supported by google


The following shows how to setup and use three different PKCS backend to host GCP Service Account and the binary which returns the `access_token``.

The binaries provided here just returns a JSON formatted oauth2.Token which you can use in any GCP Library.

For example,

	sts := oauth2.StaticTokenSource(tok)
	storageClient, err := storage.NewClient(ctx, option.WithTokenSource(sts))

You can also invoke this binary as a full TokenSource as well: see

for gcloud cli, you could apply the token directly using --access-token-file:

gcp-adc-pkcs  --svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" | jq -r '.access_token' > token.txt

gcloud storage ls --access-token-file=token.txt

Setup

For several samples below, we will use openssl to issue certificates which we will upload.

First thing is to install OpenSSL PKCS#11 engine

apt-get install -y libengine-pkcs11-openssl

Which should be at /usr/lib/x86_64-linux-gnu/engines-3/libpkcs11.so depending on your version of openssl

Then edit /etc/ssl/openssl.cnf and specify the following at the top

openssl_conf = openssl_init

[openssl_init]
engines = engine_section

[engine_section]
pkcs11 = pkcs11_section

[pkcs11_section]
engine_id = pkcs11
dynamic_path = /usr/lib/x86_64-linux-gnu/engines-3/libpkcs11.so
# MODULE_PATH = /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so
# MODULE_PATH = /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1 
init = 0

I've left the MODULE_PATH commented out for now but you will need to set those depending on which backend you're using.

if you want to test as is:

openssl engine dynamic  -pre SO_PATH:/usr/lib/x86_64-linux-gnu/engines-3/libpkcs11.so \
     -pre ID:pkcs11 -pre LIST_ADD:1  \
     -pre LOAD  -pre MODULE_PATH:/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so  -t -c


    (dynamic) Dynamic engine loading support
    [Success]: SO_PATH:/usr/lib/x86_64-linux-gnu/engines-3/libpkcs11.so
    [Success]: ID:pkcs11
    [Success]: LIST_ADD:1
    [Success]: LOAD
    [Success]: MODULE_PATH:/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so
    Loaded: (pkcs11) pkcs11 engine
    [RSA, rsaEncryption, id-ecPublicKey]
        [ available ]     

GCP Service account

Create a service account key and export its private key

export PROJECT_ID=`gcloud config get-value core/project`
gcloud iam service-accounts create tpm-sa --display-name "TPM Service Account"
export SERVICE_ACCOUNT_EMAIL=tpm-sa@$PROJECT_ID.iam.gserviceaccount.com
gcloud iam service-accounts keys create tpm-svc-account.json --iam-account=$SERVICE_ACCOUNT_EMAIL

cat tpm-svc-account.json | jq -r '.private_key' > /tmp/f.json
openssl rsa -in /tmp/f.json -out /tmp/key_rsa.pem 

openssl pkcs8 -topk8 -nocrypt -inform PEM -outform DER     -in  /tmp/key_rsa.pem     -out /tmp/key_rsa.der

SoftHSM

SoftHSM is used for testing so needless to say, its just for local stuff.

If you just want to test this, you need to install SoftHSM ofcourse and ensure the driver is present at /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so. Remember to uncomment the openssl.cnf MODULE_PATH line.

Create a file called /tmp/softhsm.conf with contents:

log.level = DEBUG
objectstore.backend = file
directories.tokendir = /tmp/tokens
slots.removable = true

Now we're ready to initialize softhsm to hold a key

export PKCS_MODULE=/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so

mkdir /tmp/tokens

export SOFTHSM2_CONF=/tmp/softhsm.conf

# initialize the hsm
pkcs11-tool --module $PKCS_MODULE --slot-index=0 --init-token --label="token1" --so-pin="123456"
pkcs11-tool --module $PKCS_MODULE  --label="token1" --init-pin --so-pin "123456" --pin mynewpin

Now that we have the HSM initialized, we'll import our service account key into it

## the list the slots, objects;  ofcourse your values will be different 
$ pkcs11-tool --module $PKCS_MODULE  --list-token-slots

    Available slots:
    Slot 0 (0x36096ef9): SoftHSM slot ID 0x36096ef9
      token label        : token1
      token manufacturer : SoftHSM project
      token model        : SoftHSM v2
      token flags        : login required, rng, token initialized, PIN initialized, other flags=0x20
      hardware version   : 2.6
      firmware version   : 2.6
      serial num         : cd386436b6096ef9
      pin min/max        : 4/255
    Slot 1 (0x1): SoftHSM slot ID 0x1
      token state:   uninitialized


pkcs11-tool --module $PKCS_MODULE \
     -l --pin mynewpin --write-object /tmp/key_rsa.der --type privkey --label keylabel1 --id 01

## now create a PKCS URI that openssl understands, remember to change the serial number
export PKCS11_URI="pkcs11:model=SoftHSM%20v2;manufacturer=SoftHSM%20project;serial=cd386436b6096ef9;token=token1;object=keylabel1?pin-value=mynewpin"

## you can print out the public key
openssl rsa -engine pkcs11  -inform engine -in "$PKCS11_URI" -pubout -out /tmp/key_pub.pem
openssl rsa -pubin -inform PEM -in /tmp/key_pub.pem -outform DER -out /tmp/key_pub.der

pkcs11-tool --module $PKCS_MODULE \
     -l --pin mynewpin --write-object /tmp/key_pub.der --type pubkey --label keylabel1 --id 01


## now you should see both the public and private key:

$ pkcs11-tool --module $PKCS_MODULE  --list-objects --pin mynewpin

    Using slot 0 with a present token (0x36096ef9)
    Public Key Object; RSA 2048 bits
      label:      keylabel1
      ID:         01
      Usage:      encrypt, verify, verifyRecover, wrap
      Access:     none
    Private Key Object; RSA 
      label:      keylabel1
      ID:         01
      Usage:      decrypt, sign, signRecover, unwrap
      Access:     sensitive

At this point we need to create a PKCS URL for that key.

Note for the URL, we're copying over the values for the PKCS Token and Object. Specifically all the values we listed above (note that the slot-id value below is from hex->decimal: 0x36096ef9 --> 906587897). Also remember to specify the serial= value to match what you see as the serial number above

export SLOT_ID=906587897
export SERIAL=cd386436b6096ef9

export FULL_PKCS11_URI="pkcs11:model=SoftHSM%20v2;manufacturer=SoftHSM%20project;slot-id=$SLOT_ID;serial=$SERIAL;token=token1;object=keylabel1;id=01?pin-value=mynewpin&module-path=/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so"


# for softHSM
# export SOFTHSM2_CONF=/tmp/softhsm.conf

CGO_ENABLED=1 go build -o gcp-adc-pkcs adc.go

./gcp-adc-pkcs --pkcsURI=$FULL_PKCS11_URI  --serviceAccountEmail=tpm-sa@$PROJECT_ID.iam.gserviceaccount.com

## output is json Token specs
{
  "access_token": "ya29.c.c0AY_VpZjqp...redacted",
  "expires_in": 3599,
  "token_type": "Bearer"
}

TPM

If you want to generate an RSA key on a TPM, you'll first need to install some supporting libraries (critically libtpm2-pkcs11-1) and ofcourse a TPM (I used a GCP Shielded VM as an example so the TPM provider is GOOG)

apt-get update && apt-get install libtpm2-pkcs11-1 tpm2-tools libengine-pkcs11-openssl opensc  gnutls -y

You should verify /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1 is installed (or at /usr/local/lib/libtpm2_pkcs11.so).

As with SoftHSM, we will initialized the device and generate an RSA key onboard:

export PKCS_MODULE=/usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1 
pkcs11-tool --module $PKCS_MODULE --slot-index=0 --init-token --label="token1" --so-pin="123456"
pkcs11-tool --module $PKCS_MODULE  --label="token1" --init-pin --so-pin "123456" --pin mynewpin

## print out the (empty) slots and generate a key
pkcs11-tool --module $PKCS_MODULE --list-token-slots
pkcs11-tool --module $PKCS_MODULE --label="keylabel1" --login  --pin=mynewpin --id 1  --keypairgen --key-type rsa:2048


## after the  keys are generated:
$ pkcs11-tool --module $PKCS_MODULE --list-slots
    Available slots:
    Slot 0 (0x1): token1                          GOOG
      token label        : token1
      token manufacturer : GOOG
      token model        : vTPM
      token flags        : login required, rng, token initialized, PIN initialized
      hardware version   : 1.42
      firmware version   : 22.17
      serial num         : 0000000000000000
      pin min/max        : 0/128
    Slot 1 (0x2):                                 GOOG
      token state:   uninitialized


$ pkcs11-tool --module $PKCS_MODULE --list-objects  --pin mynewpin
    Using slot 0 with a present token (0x1)
    Public Key Object; RSA 2048 bits
      label:      keylabel1
      ID:         01
      Usage:      encrypt, verify
      Access:     local
    Private Key Object; RSA 
      label:      keylabel1
      ID:         01
      Usage:      decrypt, sign
      Access:     sensitive, always sensitive, never extractable, local
      Allowed mechanisms: RSA-X-509,RSA-PKCS-OAEP,RSA-PKCS,SHA1-RSA-PKCS,SHA256-RSA-PKCS,SHA384-RSA-PKCS,SHA512-RSA-PKCS,RSA-PKCS-PSS,SHA1-RSA-PKCS-PSS,SHA256-RSA-PKCS-PSS,SHA384-RSA-PKCS-PSS,SHA512-RSA-PKCS-PSS

now construct the PKCS URI that reflects the values you have:

export PKCS11_URI="pkcs11:model=vTPM;manufacturer=GOOG;serial=0000000000000000;token=token1;object=keylabel1;type=private?pin-value=mynewpin"

## then print out the public key, issue the cert
$ openssl rsa -engine pkcs11  -inform engine -in "$PKCS11_URI" -pubout
        engine "pkcs11" set.
        writing RSA key
        -----BEGIN PUBLIC KEY-----
        MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA55HAxsbZyfPuBPPyLOqD
        A1hD9HygLAhAgkPVHa7BtpYPRNRA/YGiUg1ioJJlwKKgqk86fqndUi1FCIBqHcbW
        oGXJ1Bqtf/q6o8LGxAn6BwV0aLe0tDrOa0tU3VNV8A6uXiiaGBOmA49iT7iBaQtw
        sZp/JnpWJwZOrqdX4E4U0up72L/AYab4XvtWAytMPXy5W8rCWUOlKEojra864UN4
        E1ZxkxI3ywMNiLQe7XLilODbhGue5GP8XVXL4GD2zBFB4pipu4102245rgjZd0d5
        jB8kjyMKn6f/maj0qQDtZ0MrZKvjiuBVvzJR+Oj/8FL1CSJg0rl035yPzb6F2NTo
        lQIDAQAB
        -----END PUBLIC KEY-----

openssl req -new -x509 -days 365 \
  -subj "/C=US/ST=CA/L=Mountain View/O=Google/OU=Enterprise/CN=pkcs-sa" -sha256 \
   -engine pkcs11 -keyform engine -key  "$PKCS11_URI"  -out cert_tpm.pem

Now that we have the cert, we can upload it to GCP

export PROJECT_ID=`gcloud config get-value core/project`

gcloud iam service-accounts create tpm-sa

gcloud iam service-accounts keys upload cert_tpm.pem  --iam-account tpm-sa@$PROJECT_ID.iam.gserviceaccount.com
gcloud iam service-accounts keys list --iam-account=tpm-sa@$PROJECT_ID.iam.gserviceaccount.com

Regenerate the URI in the canonical format for the binary and run

export FULL_PKCS11_URI="pkcs11:model=vTPM;manufacturer=GOOG;slot-id=01;serial=0000000000000000;token=token1;object=keylabel1;id=01?pin-value=mynewpin&module-path=/usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1"

CGO_ENABLED=1 go build -o gcp-adc-pkcs adc.go
./gcp-adc-pkcs --pkcsURI=$FULL_PKCS11_URI --serviceAccountEmail=tpm-sa@$PROJECT_ID.iam.gserviceaccount.com

Finally, you may want to restrict access to the TPM device by applying tpm-udev.rules


YubiKey

(mostly copied from here)

For Yubikey, we wont' be using openssl to generate the cert but rather YubiKeys own UI to do all the work

First, you must have a YubiKey Neo or YubiKey 4 or 5 as those the additional PIV support.

You are free to provision the key by any means but for reference, the following uses:

Using the yubikey UI, click "Generate" a new self-signed rsa 2048 key. Set the CN value to the service accounts CN for simplicity.

Export the key

images/generated.png

and assign that to the service account

export PROJECT_ID=`gcloud config get-value core/project`

gcloud iam service-accounts create tpm-sa

gcloud iam service-accounts keys upload cert_yubikey.pem  --iam-account tpm-sa@$PROJECT_ID.iam.gserviceaccount.com
gcloud iam service-accounts keys list --iam-account=tpm-sa@$PROJECT_ID.iam.gserviceaccount.com

From then on, any system where you need to access GCP from you must install

so using libykcs11.so, find the specifications for your device

export PKCS_MODULE=/usr/local/lib/libykcs11.so

$  pkcs11-tool --module $PKCS_MODULE --list-token-slots
  Available slots:
  Slot 0 (0x0): Yubico YubiKey OTP+FIDO+CCID 00 00
    token label        : YubiKey PIV #13981219
    token manufacturer : Yubico (www.yubico.com)
    token model        : YubiKey YK5
    token flags        : login required, rng, token initialized, PIN initialized
    hardware version   : 1.0
    firmware version   : 5.27
    serial num         : 13981219
    pin min/max        : 6/48


$ pkcs11-tool --module $PKCS_MODULE --list-objects  --pin 123456

  Certificate Object; type = X.509 cert
    label:      X.509 Certificate for Digital Signature
    subject:    DN: CN=tpm-sa@core-eso.iam.gserviceaccount.com
    serial:     085F63F79A30D546FB522ADE8B98D1A307560A80
    ID:         02

  Public Key Object; RSA 2048 bits
    label:      Public key for Digital Signature
    ID:         02
    Usage:      encrypt, verify
    Access:     local

Then construct the URI using these values and execute the binary

export FULL_PKCS11_URI="pkcs11:model=YubiKey%20YK5;manufacturer=Yubico%20%28www.yubico.com%29;serial=13981219;slot-id=00;token=0;id=02;object=Private%20key%20for%20Digital%20Signature?pin-value=123456&module-path=/usr/local/lib/libykcs11.so"

CGO_ENABLED=1 go build -o gcp-adc-pkcs adc.go
./gcp-adc-pkcs --pkcsURI=$FULL_PKCS11_URI --serviceAccountEmail=tpm-sa@$PROJECT_ID.iam.gserviceaccount.com

Appendix

Note this sample uploads keys which can be blocked by the GCP Organization Policy constraints/iam.disableServiceAccountKeyUpload.

Finally you can also use gnutls-bin's p11tool to inspect the URIs

p11tool --login --list-token-urls --detailed-url --provider=$PKCS_MODULE
p11tool --login --list-all-privkeys  --detailed-url --provider=$PKCS_MODULE