Initial commit of Marshal code base

Change-Id: Icc44c918bb3558ca7b2d0e7d17f1dc55f5b39972
This commit is contained in:
Dave McCowan 2015-10-22 16:42:26 -04:00
parent b2aad62b28
commit 0f79632f94
46 changed files with 3695 additions and 28 deletions

126
README.md Normal file
View File

@ -0,0 +1,126 @@
## Marshal
### Overview
* Marshal is an agent service running inside virtual machines, which will be responsible for securely fetching encryption keys from ia KMS like Barbican.
* This agent will be interfacing with the disk encryption subsystem of the underlying operating system to encrypt/decrypt the disk I/O.
* In the case of Linux-based virtual machines this agent will be interfacing with dm-crypt and for Windows OS it will be interfacing with Bit-locker.
* The agent provides an abstraction service and can be integrated with other encryption subsystem as required.
* When the agent reads a key from the KMS, the key is only stored briefly in a secure temporary file until it can be transferred to the disk encryption subsystem.
**Table of Contents**
- [Overview](#overview)
- [Features](#features)
- [Architecture](#architecture)
- [Getting Started](#getting-started)
- [Software Requirements](#software-requirements)
- [Deployment Procedure](#deployment-procedure)
- [Documentation](#documentation)
- [Roadmap](#roadmap)
- [Core Components and Features](#core-components-and-features)
- [Security](#security)
- [Operations](#operations)
- [Platform Support](#platform-support)
- [Development](#development)
- [License](#license)
### Features
* Disk encryption subsystem abstraction allowing for a consistent interface
* KMS system abstraction allowing for a consistent interface
* Encryption at various levels including full disk encryption, partition encryption including root partition
### Architecture
-----------------------------------------------------------------------------------------------------------------------------
![Diagram1](docs/images/marshal_within_openstack.png)
### Getting Started
#### Deployment
#####For production purposes, Marshal is intended to be deployed as a Debian Package embedded into OpenStack VMs
###### Deploying Using Debian Package
[Building and testing debian package](docs/debian-package-building.md)
##### For test purposes, Marshal can be cloned using normal Git semantics:
#### Clone to local repository:
#####Via SSH:
```$ git clone git@github.com:CiscoCloud/marshal.git ```
#####Via HTTPS:
```$ git clone https://github.com/CiscoCloud/marshal.git ```
### Software Requirements
-----------------------------------------------------------------------------------------------------------------------------
* Python 2.7.8
* Cryptsetup (if Linux OS)
### Deployment Procedure
-----------------------------------------------------------------------------------------------------------------------------
###### Please refer to the [Getting Started Guide](docs/Getting%20Started.md), which covers deployment, configuration, and example usage.
### Documentation
###### All documentation is located [here](docs)
### Roadmap
* KMS for infrastructure tenants
* Volume encryption (With Marshal)
* Certificate provisioning
* Object Encryption
* High key use tenants and IOT
* KMaaS
### Core Components and Features
-----------------------------------------------------------------------------------------------------------------------------
###### List core components and features here
- [x] Orchestration
### Security
-----------------------------------------------------------------------------------------------------------------------------
###### List the security services it provides
- [x] Encryption
### Operations
-----------------------------------------------------------------------------------------------------------------------------
###### Disk encryption
###### Automatic key retreival from a KMS
### Platform Support
-----------------------------------------------------------------------------------------------------------------------------
###### Currently, only the Linux platform is supported using dm_crypt. Support Windows using bitlocker currently in the planning stages.
###### Currently, only the OpenStack Barbican KMS is supported. Support for other KMSs is currently in the planning stages.
###### Currently, only cloud-based KMSs are supported. Support for local KMSs is currently in the planning stages.
### Development
###### Write about the details of how anyone can contribute to the project.
### Getting Support
###### Write about the support details of the project.In case of any issue how anyone can get the support.
### License
###### Write about the license details of the project.

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

27
debian/control vendored Normal file
View File

@ -0,0 +1,27 @@
Source: marshal
Section: misc
Priority: optional
Maintainer: Marshal Team <arvtiwar@cisco.com>
Build-Depends:
debhelper (>= 9~),
python-all (>= 2.6),
Build-Depends-Indep:
python-pbr (>= 0.6),
python-setuptools,
XS-Python-Version: >= 2.6
Standards-Version: 3.9.6
Vcs-Browser: https://github.com/CiscoCloud/marshal
Vcs-Git: git://github.com/CiscoCloud/marshal.git
Package: marshal
Architecture: all
Depends:
python-six,
python-blist,
python-paste,
python-openssl,
python-requests,
${misc:Depends},
${python:Depends},
Description: CiscoCloud marshal
CiscoCloud marshal

34
debian/copyright vendored Normal file
View File

@ -0,0 +1,34 @@
Format: http://dep.debian.net/deps/dep5
Upstream-Name: marshal
Source: https://github.com/CiscoCloud/marshal
Files: *
Copyright: 2015 Cisco Systems
License: Apache-2
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
http://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.
On Debian-based systems the full text of the Apache version 2.0 license
can be found in `/usr/share/common-licenses/Apache-2.0'
Files: tools/rfc.sh
Copyright: Copyright (c) 2010-2011 Gluster, Inc
License: GPL-v3
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
.
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
.
On Debian-based systems the full text of the Apache version 2.0 license
can be found in `/usr/share/common-licenses/GPL-3'

1
debian/marshal.dirs vendored Normal file
View File

@ -0,0 +1 @@
/var/log/marshal

1
debian/marshal.install vendored Normal file
View File

@ -0,0 +1 @@
etc/marshal/marshal.conf etc/marshal

5
debian/pydist-overrides vendored Normal file
View File

@ -0,0 +1,5 @@
oslo.config python-oslo-config
oslo.serialization python-oslo-serialization
oslo.i18n python-oslo-i18n
oslo.utils python-oslo-utils
oslo.log python-oslo-log

7
debian/rules vendored Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/make -f
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
%:
dh $@ --with python2

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (native)

336
docs/Getting Started.md Normal file
View File

@ -0,0 +1,336 @@
###Marshal Getting Started Guide
####Deploying Marshal
Marshal is currently deployable for Debian-based Linux systems and a Debian package is available and has been tested in Ubuntu 14.04. The intention is to have Marshal pre-installed on certain images. However, in cases where Marshal is not pre-installed, it can be installed as a Debian package. The procedure to install the Debian package is given here:
######ToDo: get clean Debian installation procedure
To check if Marshal is installed, run the following command:
```
sudo marshal -h
```
There are several ways to configure Marshal. The primary way to configure Marshal defaults is in the /etc/marshal/marshal.conf file:
```
[DEFAULT]
# Show more verbose log output (sets INFO log level output)
# verbose = True
# Show debugging output in logs (sets DEBUG log level output)
# debug = True
# log file location
log_file = /var/log/marshal/marshal.log
[KM-OPT]
# This section intended for dev/test purposes only
# Default Auth Endpoint - for dev/test purposes only
keystone_endpoint=http://173.39.224.159:35357/v3/auth/tokens
# Default KMS fields - for dev/test purposes only
kms_base=[HOST]
kms_get_key_api=[some API]
kms_key_id=[some key id]
kms_project_id=[some project id]
[crypt]
#This section for
lf=/tmp/license.json
ci=aes-cbc-essiv:sha256
ks=256
```
More sensitive configuration details are configured in a JSON-formatted license file. The license file can come in 3 different flavors depending on the type of credentials to be used by the Marshal agent to authenticate. The 3 types are as follows:
* user-based
* certififcate
* trust
Example license configuration files for each of these 3 types are given here:
user-based license:
```
{
"license":
{
"identity": {
"version":"v3",
"endpoint":"http://[KEYSTONE_HOST]/v3/auth/tokens"
},
"project": {
"id":"f383613fbcd74d6f8f9d4a40721ef811",
"name":"marshal-demo"
},
"credentials": {
"type":"user",
"user": {
"id":"4c49397e2d9f41e392498b8079c65343",
"password":"changeit"
}
},
"key": {
"id":"e2ccc708-7c8d-437d-aaac-12bad476dd25"
}
}
}
```
certificate-based license:
```
{
"license":
{
"identity": {
"version":"v3",
"endpoint":"http://[KEYSTONE_HOST]/v3/auth/tokens"
},
"project": {
"id":"12345",
"name":"marshal-demo"
},
"credentials": {
"type":"cert",
"cert": {
"subject":"some_subject"
"signature":"some_signature"
"pub_key":"some_key"
}
},
"key": {
"id":"1b13ffdc-1b79-40ce-b94e-f4a2f9253d91"
}
}
}
```
trust-based license:
```
{
"license":
{
"identity": {
"version":"v3",
"endpoint":"http://[KEYSTONE_HOST]/v3/auth/tokens"
},
"project": {
"id":"12345",
"name":"marshal-demo"
},
"credentials": {
"type":"trust",
"trust": {
"id":"4c49397e2d9f41e392498b8079c65343"
}
},
"key": {
"id":"1b13ffdc-1b79-40ce-b94e-f4a2f9253d91"
}
}
}
```
The license path+file can be specified in the Marshal configuration file, or passed as a parameter with the '--crypt-lf' switch. Eg:
```
sudo marshal --crypt-action open --crypt-dev /dev/vdc2 --crypt-mn priv_part --crypt-lf /tmp/license.json
```
It is recommended that the license file be placed in the /tmp folder or otherwise be disposed of once the desired encryption state is achieved.
####Understanding Marshal
#####Authentication and Key retrieval
######General Behavior
Using the Keystone endpoint given by the configuration license:identity:endpoint, Marshal will attempt to authenticate using the configuration license:credentials:type and associated credential details.
######Barbican-specifc behavior
Upon successfully authenticating, Marshal will receive an OpenStack token which should provide a Barbican Key Management Store (KMS) endpoint. Marshal will then attempt to retrieve the binary key associated with the key id given in the configuration
######Other-KMS bevahior
Other KMS systems (non-Barbican) are not currently supported, but are expected to be in the near future.
#####Encryption operations
Once Marshal has successfully retrieved the key, an encryption operation can commence against that key. Currently, only volume and volume partition encryption operations are supported, but other operations are anitipated in the future possibly including encrypted communications. The Marshal 'set' action will open an encrypted volume (if it can find the key), and it will format the target using LUKS formatting, if needed. The Marshal 'unset' command will close the target.
####Example Marshal operations:
#####Formatting a block device:
```
sudo marshal --crypt-action format --crypt-dev <device> --crypt-mn <managed name>
```
Example:
```
cloud-user@marshal-test-8:~/marshal$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 50G 0 disk
└─vda1 253:1 0 50G 0 part /
vdb 253:16 0 2G 0 disk
vdc 253:32 0 2G 0 disk
sudo marshal --crypt-action format --crypt-dev /dev/vdb --crypt-mn backup
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action format --crypt-dev /dev/vdb --crypt-mn backup
Device /dev/vdb is not a valid LUKS device.
Command failed with code 22: Device /dev/vdb is not a valid LUKS device.
Could not establish /dev/vdb as a valid LUKS device.
Attempting to fetch key from KMS...
Key successfully retrieved from KMS...
The volume was successfully formatted.
```
#####Opening a block device:
```
sudo marshal --crypt-action open --crypt-dev <device> --crypt-mn <managed name>
```
Example
```
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action open --crypt-dev /dev/vdb --crypt-mn backup
/dev/vdb is a LUKS device.
Attempting to fetch key from KMS...
Key successfully retrieved from KMS...
The volume was successfully opened.
cloud-user@marshal-test-8:~/marshal$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 50G 0 disk
└─vda1 253:1 0 50G 0 part /
vdb 253:16 0 2G 0 disk
└─backup (dm-0) 252:0 0 2G 0 crypt
vdc 253:32 0 2G 0 disk
```
#####Unsetting/Closing a block device:
Unset=close. Either command can be used to achieve the same result.
```
sudo marshal --crypt-action unset --crypt-dev <device> --crypt-mn <managed name>
sudo marshal --crypt-action close --crypt-dev <device> --crypt-mn <managed name>
```
Example
```
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action close --crypt-dev /dev/vdb --crypt-mn backup
/dev/vdb is a LUKS device.
The volume was successfully closed.
cloud-user@marshal-test-8:~/marshal$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 50G 0 disk
└─vda1 253:1 0 50G 0 part /
vdb 253:16 0 2G 0 disk
vdc 253:32 0 2G 0 disk
```
#####Setting a block device:
To "set" the device means to open it as a LUKS volume if possible, and if not then LUKS format and then open the device.
```
sudo marshal --crypt-action set --crypt-dev <device> --crypt-mn <managed name>
```
Example requiring format
```
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action set --crypt-dev /dev/vdc --crypt-mn backup2
Device /dev/vdc is not a valid LUKS device.
Command failed with code 22: Device /dev/vdc is not a valid LUKS device.
Could not establish /dev/vdc as a valid LUKS device.
Attempting to fetch key from KMS...
Key successfully retrieved from KMS...
The volume was successfully formatted.
The volume was successfully opened.
cloud-user@marshal-test-8:~/marshal$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 50G 0 disk
└─vda1 253:1 0 50G 0 part /
vdb 253:16 0 2G 0 disk
└─backup (dm-0) 252:0 0 2G 0 crypt
vdc 253:32 0 2G 0 disk
└─backup2 (dm-1) 252:1 0 2G 0 crypt
```
Example not requiring format
```
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action set --crypt-dev /dev/vdb --crypt-mn backup
/dev/vdb is a LUKS device.
Attempting to fetch key from KMS...
Key successfully retrieved from KMS...
The volume was successfully opened.
cloud-user@marshal-test-8:~/marshal$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 50G 0 disk
└─vda1 253:1 0 50G 0 part /
vdb 253:16 0 2G 0 disk
└─backup (dm-0) 252:0 0 2G 0 crypt
vdc 253:32 0 2G 0 disk
└─backup2 (dm-1) 252:1 0 2G 0 crypt
```
#####Statusing a block device:
```
sudo marshal --crypt-action status --crypt-dev <device> --crypt-mn <managed name>
```
Example
```
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action status --crypt-dev /dev/vdc --crypt-mn backup2
/dev/vdc is a LUKS device.
```
#####Statusing a block device with verbosity:
Adding the -v flag enables INFO messages to appear at the CLI
```
sudo marshal --crypt-action status --crypt-dev <device> --crypt-mn <managed name> -v
```
Example
```
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action status --crypt-dev /dev/vdc --crypt-mn backup2 -v
2015-10-06 22:26:16.186 1669 INFO marshal_agent.common.config [-] status action requested.
/dev/vdc is a LUKS device.
2015-10-06 22:26:16.192 1669 INFO marshal_agent.common.config [-] Status output was: /dev/mapper/backup2 is active.
type: LUKS1
cipher: aes-cbc-essiv:sha256
keysize: 256 bits
device: /dev/vdc
offset: 4096 sectors
size: 4190208 sectors
mode: read/write
Command successful.
```
#####Overriding the license file default as set in the configiration file:
```
sudo marshal --crypt-action <action> --crypt-dev <device> --crypt-mn <managed name> --crypt-lf <license file>
```
Example
```
cloud-user@marshal-test-8:~/marshal$ mv /tmp/license.json .
License file not found.
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action status --crypt-dev /dev/vdc --crypt-mn backup2
License file not found.
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action status --crypt-dev /dev/vdc --crypt-mn backup2 --crypt-lf license.json
/dev/vdc is a LUKS device.
```
#####Overriding the cipher default as set in the configiration file:
```
sudo marshal --crypt-action <action> --crypt-dev <device> --crypt-mn <managed name> --crypt-ci <cipher>
```
Example
```
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action format --crypt-dev /dev/vdc --crypt-mn backup2 --crypt-ci aes-xts-plain64
/dev/vdc is a LUKS device.
Attempting to fetch key from KMS...
Key successfully retrieved from KMS...
The volume was successfully formatted.
```
#####Overriding the key size default as set ub the configuration file:
```
sudo marshal --crypt-action <action> --crypt-dev <device> --crypt-mn <managed name> --crypt-ks <key size>
```
Example
```
cloud-user@marshal-test-8:~/marshal$ sudo marshal --crypt-action format --crypt-dev /dev/vdc --crypt-mn backup2 --crypt-ci aes-xts-plain64 --crypt-ks 512
/dev/vdc is a LUKS device.
Attempting to fetch key from KMS...
Key successfully retrieved from KMS...
The volume was successfully formatted.
```

View File

@ -0,0 +1,35 @@
**1. Adding official Openstack Kilo PPA repository.**
It's needed because some marshal dependencies available only from it.
```
sudo apt-get install ubuntu-cloud-keyring
echo "deb http://ubuntu-cloud.archive.canonical.com/ubuntu" \
"trusty-updates/kilo main" \
| sudo tee /etc/apt/sources.list.d/cloudarchive-kilo.list
```
**2. Update index of system packages.**
```
sudo apt-get update
```
**3. Install build tools and Marshal dependencies**
```
sudo apt-get install build-essential debhelper fakeroot git python-setuptools python-pbr python-all
```
**4. Clone fresh Marshal repo**
```
git clone https://github.com/CiscoCloud/marshal
cd marshal
```
**5. Build deb package**
```
dpkg-buildpackage -us -uc
```
**6. Install package to the target system**
```
sudo dpkg -i ../marshal_*_all.deb # Errors on this step are normal, next step fixes them.
sudo apt-get -f install # (Optional) This installs broken marshal dependencies.
```
**7. And try to use it**
```
sudo marshal --help
sudo marshal.sh -h
```

93
docs/disk_commands Normal file
View File

@ -0,0 +1,93 @@
http://jootamam.net/howto-basic-cryptsetup.htm
http://www.finnie.org/2009/07/26/keyfile-based-luks-encryption-in-debian/
http://sleepyhead.de/howto/?href=cryptpart
sudo cryptsetup isLuks /dev/vdb
sudo cryptsetup -y luksFormat /dev/vdb --key-file /tmp/mykey.txt
sudo cryptsetup --key-file /tmp/mykey.txt luksOpen /dev/vdb backup
ubuntu@dm-crypt:~$ lsblk -o KNAME,TYPE,SIZE,MODEL
KNAME TYPE SIZE MODEL
vda disk 30G
vda1 part 30G
vdb disk 4G
ubuntu@dm-crypt:~$ lsblk -d -n -oNAME,RO | grep '0$' | awk {'print $1'}
vda
vdb
http://www.bogotobogo.com/DevOps/AWS/aws_attaching_Amazon_EBS_volume_to_instance.php
ubuntu@dm-crypt:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 30G 0 disk
└─vda1 253:1 0 30G 0 part /
vdb 253:16 0 4G 0 disk
ubuntu@dm-crypt:~$ sudo file -s /dev/vdb
sudo: unable to resolve host dm-crypt
/dev/vdb: data
ubuntu@dm-crypt:~$ sudo file -s /dev/vda1
sudo: unable to resolve host dm-crypt
/dev/vda1: Linux rev 1.0 ext4 filesystem data, UUID=50879377-446a-412a-99bf-e0d42a78c1b3, volume name "cloudimg-rootfs" (needs journal recovery) (extents) (large files) (huge files)
ubuntu@dm-crypt:~$ sudo mkfs -t ext4 /dev/vdb
sudo: unable to resolve host dm-crypt
mke2fs 1.42.9 (4-Feb-2014)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
262144 inodes, 1048576 blocks
52428 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=1073741824
32 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done
ubuntu@dm-crypt:~$ sudo file -s /dev/vdb
sudo: unable to resolve host dm-crypt
/dev/vdb: Linux rev 1.0 ext4 filesystem data, UUID=a6e166a8-7b98-4f61-8318-6ba4b878bfee (extents) (large files) (huge files)
ubuntu@dm-crypt:~$ sudo mkdir /mydisk
sudo: unable to resolve host dm-crypt
ubuntu@dm-crypt:~$ sudo mount /dev/vd /mydisk
vda vda1 vdb
ubuntu@dm-crypt:~$ sudo mount /dev/vdb /mydisk
sudo: unable to resolve host dm-crypt
ubuntu@dm-crypt:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 30G 0 disk
└─vda1 253:1 0 30G 0 part /
vdb 253:16 0 4G 0 disk /mydisk7379716029
http://unix.stackexchange.com/questions/52078/how-to-mount-a-cryptsetup-container-just-with-mount
#!/bin/bash
set -e
if [[ $(mount | grep ${2%%/} | wc -l) -gt 0 ]]; then
echo "Path $2 is already mounted!" >&2
exit 9
else
MAPPER=$(mktemp -up /dev/mapper)
cryptsetup luksOpen $1 $(basename $MAPPER)
shift
mount $MAPPER $* || cryptsetup luksClose $(basename $MAPPER)
fi

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

137
docs/marshal-demo.md Normal file
View File

@ -0,0 +1,137 @@
1.Create symmetric keys using Barbican Order API.
2.Inject license file into VM using a cloud-config script:
```
#cloud-config
write_files:
- content: |
{
"license":
{
"identity": {
"version":"v3",
"endpoint":"[KMS endpoint url]"
},
"project": {
"id":"f383613fbcd74d6f8f9d4a40721ef811",
"name":"marshal-demo"
},
"credentials": {
"type":"user",
"user": {
"id":"[user_id]",
"password":"[user_password]"
}
},
"key": {
"id":"[key id]"
}
}
}
owner: root:root
path: /tmp/license.json
permissions: '0644'
```
3.Create volume and attach to vm using Horizon
4.Look for the attached disk
```
lsblk
lsblk -d -n -oNAME,RO | grep '0$' | awk {'print $1'}
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 50G 0 disk
└─vda1 253:1 0 50G 0 part /
vdb 253:16 0 2G 0 disk
```
5.Set v-disk with marshal and dm-crypt
```
sudo marshal.sh set -d /dev/vdb -m vdb1 -l /tmp/license.json
```
7.Look for the attached disk
```
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 50G 0 disk
└─vda1 253:1 0 50G 0 part /
vdb 253:16 0 2G 0 disk
└─vdb1 (dm-0) 252:0 0 2G 0 crypt
```
8.Check status
```
sudo marshal.sh status -d /dev/vdb -m vdb1 -v
/dev/mapper/vdb1 is active.
type: LUKS1
cipher: aes-cbc-essiv:sha256
keysize: 256 bits
device: /dev/vdb
offset: 4096 sectors
size: 4190208 sectors
mode: read/write
Command successful.
```
9.create file system before mount
```
sudo mkfs.ext4 /dev/mapper/vdb1
```
10.Create mount point and mount the device point
```
sudo mkdir /cryptdisk1
sudo mount /dev/mapper/vdb1 /cryptdisk1
```
11.Verify file system on device
```
sudo df -PTH /dev/mapper/vdb1
or
sudo df -PTH /dev/mapper/vdb1 | awk '{print $2}' | sed -n '1!p'
```
12.Write something
```
cd /cryptdisk1
sudo vi crypttest.txt
```
13.Search content
```
sudo grep "super" /cryptdisk1/*
```
14.Unmount disk
```
sudo umount /cryptdisk1
```
15.Unset disk
```
sudo ./marshal.sh unset -d /dev/vdb -m vdb1
```
16.Search content on device
```
sudo grep "super" /dev/vdb
```

27
etc/marshal/marshal.conf Normal file
View File

@ -0,0 +1,27 @@
[DEFAULT]
# Show more verbose log output (sets INFO log level output)
# verbose = True
# Show debugging output in logs (sets DEBUG log level output)
# debug = True
# log file location
log_file = /var/log/marshal/marshal.log
[KM-OPT]
# Key Manager Options
# kms_type currently supported options are: barbican and vault. defaults to barbican
# kms_base and kms_get_ket_api are only used for non-Keystone Barbican API testing or for non-Barbican kms_type
#kms_type=barbican
kms_type=vault
#barbican
#kms_base=http://173.39.229.98:9311/v1
#kms_get_key_api=/secrets/
#vault
#kms_base=http://173.39.225.119:80/v1
kms_get_key_api=/secret/project/name/apikey
[crypt]
lf=/tmp/license.json
ci=aes-cbc-essiv:sha256
ks=256

View File

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pbr.version
__version__ = pbr.version.VersionInfo(
'marshal').version_string()

View File

119
marshal_agent/agent/auth.py Normal file
View File

@ -0,0 +1,119 @@
# Copyright (c) 2015 Cisco Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Class to handle Marshal authentication against Keystone
"""
import requests
import json
import abc
import six
from marshal_agent.i18n import _
from marshal_agent.common import config
from marshal_agent.common import exception
CONF = config.CONF
LOG = config.LOG
@six.add_metaclass(abc.ABCMeta)
class AuthBase(object):
@abc.abstractmethod
def get_token_wrapper(self, key_id, project_id):
raise NotImplementedError
def get_key_wrapper(self, key_id, project_id):
raise NotImplementedError
def get_key_binary(self):
raise NotImplementedError
class Auth(AuthBase):
def __init__(self, conf=CONF, lic=None, kms_type=None):
self.conf = conf
self.lic = lic
self.kms_type = kms_type
def get_token_wrapper(self):
return self._get_token_from_keystone()
def get_token(self):
if self.kms_type == 'vault':
token = self.lic.x_vault_token
return token, None
else:
return self._get_token_from_keystone()
def get_key_wrapper(self):
return self._get_key_from_kms()
def _get_token_from_keystone(self):
""" Get token from Keystone"""
token = None
kms_endpoint = None
payload = {
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"id": self.lic.user_id,
"password": self.lic.user_pass
}
}
},
"scope": {
"project": {
"id": self.lic.project_id,
"domain": {
"id": "default"
},
"name": self.lic.project_name
}
}
}
}
self.json_data = json.dumps(payload)
hdrs = {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=UTF-8'
}
pr = requests.post(self.lic.keystone_endpoint,
data=json.dumps(payload), headers=hdrs)
if pr.status_code != 201:
log_msg = _('Unable to get identity from Keystone. Response Code\
was: '+str(pr.status_code))
client_msg = _('Marshal was unable to authenticate.')
raise exception.MarshalHTTPException(log_msg, client_msg,
pr.status_code)
else:
LOG.debug("Successfully authenticated against Keystone.")
token = pr.headers['X-Subject-Token']
pr_j = json.loads(pr.content)
catalog = pr_j['token']['catalog']
for endpoint in catalog:
if endpoint.get('type') == 'kms':
kms_endpoint = endpoint
break
return token, kms_endpoint

View File

@ -0,0 +1,165 @@
# Copyright (c) 2015 Cisco Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Class to handle KMS key retrieval
"""
import requests
import json
import abc
import six
from marshal_agent.i18n import _
from marshal_agent.common import config
from marshal_agent.common import exception
CONF = config.CONF
LOG = config.LOG
@six.add_metaclass(abc.ABCMeta)
class KeyAgentBase(object):
@abc.abstractmethod
def get_key_wrapper(self, key_id, project_id):
raise NotImplementedError
def get_key_binary(self):
raise NotImplementedError
class KeyRunner(KeyAgentBase):
def __init__(self, lic=None, token=None, endpoint=None, conf=CONF,
key_id=None, project_id=None):
'''
if conf is not None:
CONF = conf;
else:
CONF = config.CONF
LOG = config.LOG
self.LOG = LOG
# LOG = CONF.LOG
# self.conf = conf
# self.conf = conf
#CONF = conf.CONF
'''
self.config = config
self.conf = CONF
KM_OPT_GRP_NAME = config.KM_OPT_GRP_NAME
conf_opts = getattr(CONF, KM_OPT_GRP_NAME)
self.kms_type = conf_opts.kms_type.lower()
if self.kms_type is None or self.kms_type == 'barbican':
# For Barbican, we normally get endpoint from Keystone, but this
# allows bypass of keystone for testing Barbican interface:
if lic is None:
if key_id is None or project_id is None:
self.key_id = conf_opts.kms_key_id
self.project_id = conf_opts.kms_project_id
else:
self.key_id = key_id
self.project_id = project_id
else:
self.project_id = lic.project_id
self.key_id = lic.key_id
if endpoint is None:
# Allows manually configured Barbican or other KMS:
self.kms_endpoint = conf_opts.kms_base+conf_opts.\
kms_get_key_api
LOG.debug('Using Barbican endpoint from CONF')
else:
self.kms_endpoint = endpoint.get('endpoints')[0].\
get('url')+'/secrets/'
LOG.debug('Using Barbican endpoint from Keystone')
elif self.kms_type == 'vault':
# For vault we want to be able to provide kms endpoint details in
# license file or conf file or, some mixture of the two.
# Give license file the priority
try:
self.kms_base = lic.kms_base
except AttributeError:
if conf_opts.kms_base is not None:
self.kms_base = conf_opts.kms_base
try:
self.kms_get_key_api = lic.kms_get_key_api
except AttributeError:
if conf_opts.kms_get_key_api is not None:
self.kms_get_key_api = conf_opts.kms_get_key_api
try:
self.kms_endpoint = self.kms_base+self.kms_get_key_api
except AttributeError:
raise exception.MissingKMSConfigurationError
self.lic = lic
self.token = token
def get_key_wrapper(self, key_id, project_id):
return self._get_key_from_kms(key_id, project_id)
def get_key_binary(self):
return self._get_key_from_kms(accept='application/octet-stream')
def _get_key_from_kms(self, accept=None):
if self.kms_type is None or self.kms_type == 'barbican':
if accept:
headers = {
'Accept': accept,
'X-Project-Id': self.project_id
}
else:
headers = {
'Accept': 'application/json',
'X-Project-Id': self.project_id
}
if self.token is not None:
headers['X-Auth-Token'] = self.token
key_manager_url = self.kms_endpoint+format(self.key_id)
elif self.kms_type == 'vault':
if self.token is not None:
headers = {
'Accept': 'application/json'
}
headers['X-Vault-Token'] = self.token
key_manager_url = self.kms_endpoint
LOG.debug('Calling KMS API at: %s', key_manager_url)
content = None
r = requests.get(key_manager_url, headers=headers)
if r.status_code != 200:
log_msg = _('Unable to get key from KMS. Response Code was: ' +
str(r.status_code))
client_msg = _('Unable to get key from KMS')
raise exception.MarshalHTTPException(log_msg, client_msg,
r.status_code)
elif r.content is None or r.content == '' or r.content == 'None':
LOG.info('KMS returned a blank key!')
else:
LOG.info('Successfully retrieved key from KMS.')
content = r.content
if self.kms_type is None or self.kms_type == 'barbican':
key = content
elif self.kms_type == 'vault':
try:
gr_j = json.loads(content)
key = gr_j['data']['value']
except (ValueError, KeyError, TypeError):
msg = _('Unable to parse JSON response from Key Manager')
raise exception.PayloadDecodingError(msg)
return key

View File

@ -0,0 +1,84 @@
# Copyright (c) 2015 Cisco Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Utility class to handle license data extraction and management
"""
import sys
import json
from marshal_agent.common import config
LOG = config.LOG
class License(object):
def __init__(self, license_file, kms_type=None):
self.keystone_endpoint = None
self.project_id = None
self.project_name = None
self.key_id = None
self.mode = None
self.user_id = None
self.user_pass = None
self.kms_type = kms_type
self.parse_license_file(license_file)
def parse_license_file(self, lf):
try:
with open(lf, 'r') as lf_h:
l_data = (lf_h.read()).rstrip()
try:
jl_data = json.loads(l_data)
except ValueError as e:
LOG.debug("Unable to parse license file: %s", str(e))
print 'Unable to parse license file: '+str(e)
sys.exit(1)
if self.kms_type == 'vault':
self.x_vault_token = \
jl_data['license']['identity']['token']
try:
self.kms_base = \
jl_data['license']['endpoint']['kms_base']
except KeyError:
pass
try:
self.kms_get_key_api = \
jl_data['license']['endpoint']['kms_get_key_api']
except KeyError:
pass
else:
self.keystone_endpoint = \
jl_data['license']['identity']['endpoint']
self.project_id = jl_data['license']['project']['id']
self.project_name = jl_data['license']['project']['name']
self.key_id = jl_data['license']['key']['id']
self.mode = jl_data['license']['credentials']['type']
if self.mode == 'user':
self.user_id = \
jl_data['license']['credentials']['user']['id']
self.user_pass = \
jl_data['license']['credentials']['user']['password\
']
except IOError:
LOG.info("Unable to locate license file: %s", lf)
print 'License file not found.'
sys.exit(1)
except KeyError as e:
LOG.info("Unable to parse license file: %s", str(e))
print 'Unable to parse license file.'
sys.exit(1)

View File

@ -0,0 +1,210 @@
# Copyright (c) 2015 Cisco Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The Marshal module is intended to facilitate flexible volume encryption with
dynamic key management
"""
import os
import sys
import errno
import tempfile
from stat import S_ISBLK
from marshal_agent.agent.keyRunner import KeyRunner
from marshal_agent.agent.license import License
from marshal_agent.agent.auth import Auth
from marshal_agent.agent.volCrypt import VolCrypt
from marshal_agent.common import config
from marshal_agent.common.exception import MarshalHTTPException
from marshal_agent.common.exception import PayloadDecodingError
from marshal_agent.common.exception import MissingKMSConfigurationError
CONF = config.CONF
LOG = config.LOG
def check_block_device(device):
try:
mode = os.stat(device).st_mode
except OSError as e:
print 'The specified device is invalid.'
if e[0] == errno.ENOENT:
LOG.info('Device %s was specified, but the file could not be found.\
', device)
sys.exit(1)
if not S_ISBLK(mode):
print 'The specified device is invalid.'
LOG.info('Device was specified, but the file is not a valid block devic\
e file.')
sys.exit(1)
def check_if_luks(vc, device, managed_name):
LOG.debug('Performing isLuks check.')
if device is None:
print 'Please provide the device path.'
LOG.info('isLuks check cancelled. No device specified.')
sys.exit(1)
check_block_device(device)
response = vc.is_luks(device)
if response:
print '{} is a LUKS device.'.format(device)
return True
else:
print 'Could not establish {} as a valid LUKS device.'.format(device)
return False
def missing_managed_name():
LOG.info("Managed name is a required parameter for this operation.")
print 'Please provide the managed name.'
sys.exit(1)
def agent_main():
LOG.info('Starting Marshal...')
KM_OPT_GRP_NAME = config.KM_OPT_GRP_NAME
km_conf_opts = getattr(CONF, KM_OPT_GRP_NAME)
kms_type = km_conf_opts.kms_type.lower()
VOL_CRYPT_GRP_NAME = config.VOL_CRYPT_GRP_NAME
vc_conf_opts = getattr(CONF, VOL_CRYPT_GRP_NAME)
action = vc_conf_opts.action
device = vc_conf_opts.dev
managed_name = vc_conf_opts.mn
license_file = vc_conf_opts.lf
key_size = str(vc_conf_opts.ks)
cipher = vc_conf_opts.ci
LOG.info('KMS type: %s', kms_type)
lic = License(license_file, kms_type)
vc = VolCrypt(device, managed_name)
action = action.lower()
LOG.info('%s action requested.', action)
if action == 'unset':
action = 'close'
if action == 'isluks':
check_if_luks(vc, device, managed_name)
sys.exit(1)
elif action == 'open' or action == 'format' or action == 'set':
setNeedsFormat = False
if not check_if_luks(vc, device, managed_name):
if action == 'open':
# For debugging, comment this exit and uncomment the pass
sys.exit(1)
# pass
elif action == 'set':
setNeedsFormat = True
auth = Auth(CONF, lic, kms_type)
try:
token, kms_endpoint = auth.get_token()
except MarshalHTTPException:
print 'Authentication failed.'
LOG.info('Unable to authenticate against Keystone.')
sys.exit(1)
try:
key_runner = KeyRunner(lic, token, kms_endpoint)
except MissingKMSConfigurationError as e:
LOG.info(e.message)
print e.message
sys.exit(1)
try:
print 'Attempting to fetch key from KMS...'
binary_key = key_runner.get_key_binary()
print 'Key successfully retrieved from KMS...'
except PayloadDecodingError:
LOG.info('Unable to parse KMS response.')
print 'Unable to parse KMS response.'
sys.exit(1)
except MarshalHTTPException as e:
LOG.info('Error requesting key from from KMS: %s', e.status_code)
print 'Unable to access KMS.'
sys.exit(1)
tmpdir = tempfile.mkdtemp()
key_file_name = 'key.bin'
# Ensure the file is read/write by the creator only
saved_umask = os.umask(0077)
path = os.path.join(tmpdir, key_file_name)
try:
with open(path, "wb") as tmp:
tmp.write(binary_key)
if action == 'open':
if managed_name is None:
missing_managed_name()
result = vc.open_volume(key_file=path)
if result == 0:
print 'The volume was successfully opened.'
elif action == 'format':
result = vc.format_volume(cipher=cipher, key_size=key_size,
key_file=path)
if result == 0:
print 'The volume was successfully formatted.'
elif action == 'set':
if setNeedsFormat:
result = vc.format_volume(cipher=cipher, key_size=key_size,
key_file=path)
if result == 0:
print 'The volume was successfully formatted.'
if managed_name is None:
missing_managed_name()
result = vc.open_volume(key_file=path)
if result == 0:
print 'The volume was successfully opened.'
elif result == 2:
LOG.info("Open failed on set. Attempting format...")
result = vc.format_volume(cipher=cipher, key_size=key_size,
key_file=path)
if result == 0:
print 'The volume was successfully formatted.'
result = vc.open_volume(key_file=path)
if result == 0:
print 'The volume was successfully opened.'
except IOError as e:
print 'IOError'
else:
pass
finally:
os.remove(path)
os.umask(saved_umask)
os.rmdir(tmpdir)
elif action == 'close':
if not check_if_luks(vc, device, managed_name):
sys.exit(1)
if managed_name is None:
missing_managed_name()
result = vc.close_volume()
if result == 0:
print 'The volume was successfully closed.'
elif action == 'status':
if not check_if_luks(vc, device, managed_name):
sys.exit(1)
if managed_name is None:
missing_managed_name()
vc.status_volume()
if __name__ == '__main__':
agent_main()

View File

@ -0,0 +1,135 @@
# Copyright (c) 2015 Cisco Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The volcrypt module interfaces with cryptsetup
"""
from marshal_agent.common import config
import subprocess
CONF = config.CONF
LOG = config.LOG
class VolCrypt():
def __init__(self, dev_path, mapped_name):
self.dev_path = dev_path
self.mapped_name = mapped_name
def is_luks(self, dev_path):
"""Checks if the specified device uses LUKS for encryption.
:param device: the device to check
:returns: true if the specified device uses LUKS; false otherwise
"""
try:
# check to see if the device uses LUKS: exit status is 0
# if the device is a LUKS partition and non-zero if not
cmd = ["cryptsetup", 'isLuks', '--verbose', dev_path]
output = subprocess.check_output(cmd, shell=False)
LOG.debug(output)
return True
except subprocess.CalledProcessError as e:
LOG.info(("isLuks exited with (status %(exit_code)s): "),
{"exit_code": e.returncode})
return False
def open_volume(self, **kwargs):
"""Opens the LUKS partition on the volume using the provided key file
"""
LOG.debug("opening encrypted volume %s", self.dev_path)
try:
cmd = ["cryptsetup", "luksOpen"]
key_file = kwargs.get("key_file", None)
if key_file is not None:
cmd.extend(["--key-file", key_file])
cmd.extend([self.dev_path])
cmd.extend([self.mapped_name])
output = subprocess.check_output(cmd, shell=False)
LOG.info("Successfully opened the volume. Opening output was: %s",
output)
return 0
except subprocess.CalledProcessError as e:
LOG.info(("luksOpen exited with (status %(exit_code)s)"),
{"exit_code": e.returncode})
return e.returncode
def close_volume(self, **kwargs):
"""Closes the LUKS partition on the volume
"""
LOG.debug("closing encrypted volume %s", self.dev_path)
try:
cmd = ["cryptsetup", "luksClose"]
cmd.extend([self.mapped_name])
output = subprocess.check_output(cmd, shell=False)
LOG.info("Successfully closed the volume. Closing output was: %s",
output)
return 0
except subprocess.CalledProcessError as e:
LOG.info(("luksClose exited with (status %(exit_code)s): "),
{"exit_code": e.returncode})
return e.returncode
def format_volume(self, **kwargs):
"""Creates a LUKS header on the volume.
"""
LOG.debug("formatting encrypted volume %s", self.dev_path)
try:
cmd = ["cryptsetup", "--batch-mode", "luksFormat"]
cipher = kwargs.get("cipher", None)
if cipher is not None:
cmd.extend(["--cipher", cipher])
key_size = kwargs.get("key_size", None)
if key_size is not None:
cmd.extend(["--key-size", key_size])
key_file = kwargs.get("key_file", None)
if key_file is not None:
cmd.extend(["--key-file", key_file])
cmd.extend([self.dev_path])
output = subprocess.check_output(cmd, shell=False)
LOG.info("Successfully formatted the volume. Format output was: \
%s", output)
return 0
except subprocess.CalledProcessError as e:
LOG.info(("luksFormat exited with (status %(exit_code)s): "),
{"exit_code": e.returncode})
return e.returncode
def status_volume(self, **kwargs):
"""Statuses the LUKS partition on the volume
"""
LOG.debug("Stating encrypted volume %s", self.dev_path)
try:
cmd = ["cryptsetup", "-v", "status"]
cmd.extend([self.mapped_name])
output = subprocess.check_output(cmd, shell=False)
LOG.info("Status output was: %s", output)
except subprocess.CalledProcessError as e:
LOG.info(("status exited with (status %(exit_code)s): "),
{"exit_code": e.returncode})

0