Skip to content

Commit

Permalink
Merge pull request #5 from connectedcars/ssh-certs
Browse files Browse the repository at this point in the history
Create initial version for signing certs with KMS key
  • Loading branch information
tlbdk authored Jul 27, 2020
2 parents 9b49a56 + f7a2021 commit 64a6351
Show file tree
Hide file tree
Showing 22 changed files with 1,401 additions and 595 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
.vscode
/auth-wrapper
vendor
/dist
authorized_keys
53 changes: 42 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,56 @@ RUN go version

ENV GO111MODULE=on

RUN CGO_ENABLED=0 GOOS=linux go build -o auth-wrapper -ldflags "-X 'main.versionString=$VERSION'" ./cmd
RUN CGO_ENABLED=0 GOOS=linux go build -o auth-wrapper -ldflags "-X 'main.versionString=$VERSION'" ./cmd/authwrapper

# Production image
FROM ${WRAP_IMAGE} as production
RUN echo nobody:x:65534:65534:nobody:/: > password.minimal

#
# Auth-wrapper server image
#
FROM scratch as main

ARG WRAP_COMMAND
ARG WRAP_NAME
ARG SSH_KEY_PATH

COPY --from=builder /app/auth-wrapper /opt/bin/auth-wrapper
RUN ln -s /opt/bin/auth-wrapper /opt/bin/${WRAP_NAME}
COPY --from=builder /app/password.minimal /etc/password

# Used by git image
ENV GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
USER nobody

ENTRYPOINT ["/opt/bin/auth-wrapper"]

#
# Authwrapped git with KMS keys
#
FROM gcr.io/cloud-builders/git as git-kms

# Force google tools to use the DNS name so we can overwrite it in docker
ENV GCE_METADATA_HOST=metadata.google.internal
ARG SSH_KEY_PATH

COPY --from=builder /app/auth-wrapper /opt/bin/auth-wrapper
RUN ln -s /opt/bin/auth-wrapper /opt/bin/git

ENV GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"

ENV PATH=/opt/bin:${PATH}
ENV WRAP_COMMAND=${WRAP_COMMAND}
ENV WRAP_COMMAND=git
ENV SSH_KEY_PATH=${SSH_KEY_PATH}
ENTRYPOINT ["/opt/bin/auth-wrapper"]


#
# Authwrapped git with local keys
#
FROM gcr.io/cloud-builders/git as git-local

COPY --from=builder /app/auth-wrapper /opt/bin/auth-wrapper
RUN ln -s /opt/bin/auth-wrapper /opt/bin/git

COPY build.pem /
RUN chmod 600 /build.pem

ENV GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"

ENV PATH=/opt/bin:${PATH}
ENV WRAP_COMMAND=git
ENV SSH_KEY_PATH=/build.pem
ENTRYPOINT ["/opt/bin/auth-wrapper"]
122 changes: 75 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,115 @@
# Auth wrapper

Simple wrapper that exposes an ssh-agent to all sub processes using keys from Google Cloud KMS or OpenSSH pem formated key.
Command wrapper that exposes an ssh-agent to all sub processes with keys and ssh certs backed by Google Cloud KMS or local OpenSSH pem formatted keys.

This can fx be used in CI/CD pipelines when checking code out, running package installers pulling code from private repos.
This can be used in:

* CI/CD pipelines when checking code out, running package installers pulling code from private repos.
* Auditing and restricting access to distributed SSH servers in a central location

## How to use

### Git checkout

Git clone with key store in Google Cloud KMS:

``` bash
export SSH_KEY_PATH=kms://projects/yourprojectname/locations/global/keyRings/yourkeyring/cryptoKeys/ssh-key/cryptoKeyVersions/1
auth-wrapper git clone git@github.com:connectedcars/private-module.git
```

Docker buildkit build with a key stored in Google Cloud KMS:
Git clone with local key:

``` bash
export SSH_KEY_PATH=kms://projects/yourprojectname/locations/global/keyRings/yourkeyring/cryptoKeys/ssh-key/cryptoKeyVersions/1
export PROGRESS_NO_TRUNC=1
export DOCKER_BUILDKIT=1
# The strings $SSH_AUTH_SOCK and $$SSH_AUTH_SOCK will be replaced with socket in the arguments
auth-wrapper docker --progress=plain --ssh=default='\$SSH_AUTH_SOCK' . # Note the escape to make sure we don't use the shells SSH_AUTH_SOCK
export SSH_KEY_PATH=build.pem
export SSH_KEY_PASSWORD=thepassword
auth-wrapper git clone git@github.com:connectedcars/private-module.git
```

[Dockerfile](./testdata/Dockerfile)

Google Cloud build with Docker buildkit build:

``` yaml
steps:
# Pull a modern version of docker
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'gcr.io/cloud-builders/docker:latest']
# Workaround for https://github.com/moby/moby/issues/39120
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'docker/dockerfile:experimental']
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'docker/dockerfile:1.0-experimental']
# Build container injecting SSH agent socket
- name: 'gcr.io/$PROJECT_ID/auth-wrapper-docker.master:latest'
args: ['build', '--progress=plain', '--ssh=default=$$SSH_AUTH_SOCK', '-tag=gcr.io/$PROJECT_ID/$REPO_NAME.$BRANCH_NAME:$COMMIT_SHA', '.']
env:
- "SSH_KEY_PATH=kms://projects/$PROJECT_ID/locations/global/keyRings/cloudbuilder/cryptoKeys/ssh-key/cryptoKeyVersions/1"
- "PROGRESS_NO_TRUNC=1"
- "DOCKER_BUILDKIT=1"
images: ['gcr.io/$PROJECT_ID/$REPO_NAME.$BRANCH_NAME']
### SSH Certs

Signing server:

The signing server issues a certificate based on an allow list in authorized keys file format:

http://man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT

Example file:

authorized_keys:

``` text
# Only allow this public key access from 192.168.1.0/24 and to run command "echo hello" with principal name "user1,serverType"
restrict,command="echo hello",from="192.168.1.0/24",principals="user1,serverType" ecdsa-sha2-nistp256 AAAA...C (copy from output of client) user1@company.com
# Only allow this public key access with principal name "user2"
restrict,principals="user2" ssh-rsa AAAA...D(copy from output of client) user2@company.com
# Only allow sftp access with principal name "user3"
restrict,principals="user3",command=internal-sftp AAAA...E (copy from output of client) user3@company.com
```

Git clone with local key:
Starting the server:

``` bash
export SSH_KEY_PATH=build.pem
export SSH_KEY_PASSWORD=thepassword
auth-wrapper git clone git@github.com:connectedcars/private-module.git
export SSH_SIGNING_SERVER_LISTEN_ADDRESS=":3080"
export SSH_CA_KEY_PATH="kms://projects/yourprojectname/locations/global/keyRings/ssh-keys/cryptoKeys/ssh-key/cryptoKeyVersions/1"
export SSH_CA_AUTHORIZED_KEYS_PATH="authorized_keys"
export SSH_SIGNING_LIFETIME="60m"
auth-wrapper
```

Using the client:

``` bash
export SSH_KEY_PATH=kms://projects/yourprojectname/locations/global/keyRings/yourkeyring/cryptoKeys/ssh-key/cryptoKeyVersions/1
export SSH_SIGNING_SERVER_URL="http://localhost:3080"
auth-wrapper -p user1 ssh 1.2.3.4
auth-wrapper -p serverType:gw ssh 1.2.3.4 # Use wildcard match
```

SSH Server:

To configure a SSH server to trust the signing server CA for a specific user:

~/.ssh/authorized_keys:

``` text
cert-authority,principals="user1,serverType:gw" ssh-rsa AAAA...(copy from output of signing server) ca key
```

## Options

Environment variables:
### Arguments

* -principals : Principals to request

### Environment variables

Client options:

* SSH_KEY_PATH: Path to SSH key, can be OpenSSH PEM formated key or a url to KMS key
* SSH_KEY_PASSWORD: Password to key, only used by PEM formated key
* WRAP_COMMAND: Command to run with the arguments to auth-wrapper
* SSH_SIGNING_SERVER_URL: Url for the signing server
* SSH_PRINCIPALS: Principals to request

Signing server options:

* SSH_SIGNING_SERVER_LISTEN_ADDRESS: Listen address in the following format ":8080"
* SSH_CA_KEY_PATH: Path to CA signing key, only KMS keys supported at the moment and limited to "Elliptic Curve P-256 key
SHA256 Digest"
* SSH_CA_AUTHORIZED_KEYS_PATH": Path to authorized_keys following [AUTHORIZED_KEYS_FILE_FORMAT](http://man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT)

## Google Cloud KMS key setup

Create keyring and key:

``` bash
# Create keyring for cloud build keys
gcloud kms keyrings create --location global cloudbuild
# Create keyring
gcloud kms keyrings create --location global ssh-keys
# It needs to be be SHA512 as the ssh client seems to default to this hashing algorithm and KMS pairs key size and hashing algorithms for some reason.
gcloud kms keys create ssh-key --keyring cloudbuilder --location global --default-algorithm rsa-sign-pkcs1-4096-sha512 --purpose asymmetric-signing
gcloud kms keys create ssh-key --keyring ssh-keys --location global --default-algorithm rsa-sign-pkcs1-4096-sha512 --purpose asymmetric-signing
# Give cloud build access to use the key
gcloud kms keys add-iam-policy-binding ssh-key --keyring=cloudbuilder --location=global --member serviceAccount:projectserviceaccount@cloudbuild.gserviceaccount.com --role roles/cloudkms.signerVerifier
```

Extract public key and convert to ssh format:

``` bash
gcloud kms keys versions get-public-key 1 --key ssh-key --keyring=cloudbuilder --location=global > ssh-key.pem
# Copy the output to a github user
ssh-keygen -f ssh-key.pem -i -mPKCS8
gcloud kms keys add-iam-policy-binding ssh-key --keyring=ssh-keys --location=global --member user@company.com --role roles/cloudkms.signerVerifier
```

## Local key
Expand Down
91 changes: 24 additions & 67 deletions cloudbuild.yaml
Original file line number Diff line number Diff line change
@@ -1,65 +1,32 @@
steps:
# Pull a modern version of docker
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'gcr.io/cloud-builders/docker:latest']
# Workaround for https://github.com/moby/moby/issues/39120
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'docker/dockerfile:experimental']
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'docker/dockerfile:1.0-experimental']
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'docker.io/docker/dockerfile-copy:v0.1.9']
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'ubuntu:19.10']
# Check version
- name: 'gcr.io/cloud-builders/docker'
args: ['version']
#
# Build KMS auth wrappers
#
# TODO: Move to own Dockerfile's, this is getting a bit too fancy
# Build auth wrapped git
# Build auth wrapped server container
- name: 'gcr.io/cloud-builders/docker'
args: [
'build',
'--build-arg=WRAP_IMAGE=gcr.io/cloud-builders/git',
'--build-arg=WRAP_COMMAND=/usr/bin/git',
'--build-arg=WRAP_NAME=git',
'--build-arg=SSH_KEY_PATH=kms://projects/connectedcars-staging/locations/global/keyRings/cloudbuilder/cryptoKeys/ssh-key/cryptoKeyVersions/3',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-git.$BRANCH_NAME:$COMMIT_SHA',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-git.$BRANCH_NAME:latest',
'--target=main',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME.$BRANCH_NAME:$COMMIT_SHA',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME.$BRANCH_NAME:latest',
'.'
]
# Build auth wrapped docker
# Build auth wrapped git
- name: 'gcr.io/cloud-builders/docker'
args: [
'build',
'--build-arg=WRAP_IMAGE=gcr.io/cloud-builders/docker',
'--build-arg=WRAP_COMMAND=/usr/bin/docker',
'--build-arg=WRAP_NAME=docker',
'--target=git-kms',
'--build-arg=SSH_KEY_PATH=kms://projects/connectedcars-staging/locations/global/keyRings/cloudbuilder/cryptoKeys/ssh-key/cryptoKeyVersions/3',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-docker.$BRANCH_NAME:$COMMIT_SHA',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-docker.$BRANCH_NAME:latest',
'.'
]
# Test auth wrapped docker using KSM key
- name: 'gcr.io/$PROJECT_ID/$REPO_NAME-docker.$BRANCH_NAME:$COMMIT_SHA'
args: [
'build',
'--no-cache',
'--progress=plain',
'--ssh=default=$$SSH_AUTH_SOCK',
#'--network=host',
#'--add-host=metadata.google.internal:127.0.0.1',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-git.$BRANCH_NAME:$COMMIT_SHA',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-git.$BRANCH_NAME:latest',
'.'
]
dir: 'testdata'
env:
- "PROGRESS_NO_TRUNC=1"
- "DOCKER_BUILDKIT=1"
# Test auth wrapped git using KSM key
- name: 'gcr.io/$PROJECT_ID/$REPO_NAME-git.$BRANCH_NAME:$COMMIT_SHA'
args: ['clone', 'git@github.com:connectedcars/private-module.git']
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args: ['-c', 'rm -rf private-module']
#
# Build embedded key auth wrappers
#
Expand All @@ -68,40 +35,30 @@ steps:
args: [
'cp',
'gs://connectedcars-staging-cloudbuilder-private/build.pem',
'./localkey'
'./build.pem'
]
# Build auth wrapper docker image
# Build auth wrapper git image
- name: 'gcr.io/cloud-builders/docker'
dir: localkey
args: [
'build',
'--build-arg=FROM_IMAGE=gcr.io/$PROJECT_ID/$REPO_NAME-docker.$BRANCH_NAME:$COMMIT_SHA',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-docker-pemkey.$BRANCH_NAME:$COMMIT_SHA',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-docker-pemkey.$BRANCH_NAME:latest', '.'
'--target=git-local',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-git-local.$BRANCH_NAME:$COMMIT_SHA',
'--tag=gcr.io/$PROJECT_ID/$REPO_NAME-git-local.$BRANCH_NAME:latest', '.'
]
# Test cloud build wrapper using ssh key embedded in the container
- name: 'gcr.io/$PROJECT_ID/$REPO_NAME-docker-pemkey.$BRANCH_NAME:$COMMIT_SHA'
args: [
'build',
'--no-cache',
'--progress=plain',
'--ssh=default=$$SSH_AUTH_SOCK',
#'--network=host',
#'--add-host=metadata.google.internal:127.0.0.1',
'.'
]
dir: 'testdata'
# Test auth wrapped git using local key
- name: 'gcr.io/$PROJECT_ID/$REPO_NAME-git.$BRANCH_NAME:$COMMIT_SHA'
args: ['clone', 'git@github.com:connectedcars/private-module.git']
secretEnv:
- 'SSH_KEY_PASSWORD'
env:
- "PROGRESS_NO_TRUNC=1"
- "DOCKER_BUILDKIT=1"
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args: ['-c', 'rm -rf private-module']
secrets:
- kmsKeyName: projects/connectedcars-staging/locations/global/keyRings/cloudbuilder/cryptoKeys/connectedcars-builder
secretEnv:
SSH_KEY_PASSWORD: CiQAg7wCPfO2Tf9mtZoFWjAtX7whQ481af3gyGdM9WNK26B74UkSUQBefMgeHNh0KTsGybKReXDsFcbmed7f5sw97zSe9cswpKogENM5Ye0jiIu6NfebUpCnmJ9HVHmD/yBknlW4nn1VXBs7HYGiBSFZ52i2HyEopw==
images: [
'gcr.io/$PROJECT_ID/$REPO_NAME.$BRANCH_NAME',
'gcr.io/$PROJECT_ID/$REPO_NAME-git.$BRANCH_NAME',
'gcr.io/$PROJECT_ID/$REPO_NAME-docker.$BRANCH_NAME',
'gcr.io/$PROJECT_ID/$REPO_NAME-docker-pemkey.$BRANCH_NAME'
'gcr.io/$PROJECT_ID/$REPO_NAME-git-local.$BRANCH_NAME'
]
Loading

0 comments on commit 64a6351

Please sign in to comment.