Add AAD X.509 cert auth support (#10)

- AAD/Keyvault credential support in credentials.json
This commit is contained in:
Fred Park 2017-01-10 11:21:11 -08:00
Родитель be04d89410
Коммит 348ceebc65
12 изменённых файлов: 607 добавлений и 81 удалений

4
.gitignore поставляемый
Просмотреть файл

@ -81,10 +81,6 @@ celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject

Просмотреть файл

@ -49,6 +49,8 @@ for MPI on HPC low-latency Azure VM instances:
* [N-Series](https://docs.microsoft.com/en-us/azure/virtual-machines/virtual-machines-windows-sizes?toc=%2fazure%2fvirtual-machines%2fwindows%2ftoc.json#n-series): STANDARD\_NC24R (not yet ready with Linux hosts)
* Automatic setup of SSH users to all nodes in the compute pool and optional
tunneling to Docker Hosts on compute nodes
* Support for credential management through
[Azure KeyVault](https://azure.microsoft.com/en-us/services/key-vault/)
## Installation
Installation is typically an easy two-step process. The CLI is also available

Просмотреть файл

@ -1,5 +1,18 @@
{
"credentials": {
"keyvault": {
"uri": "",
"credentials_secret_id": "",
"aad": {
"directory_id": "",
"application_id": "",
"auth_key": "",
"rsa_private_key_pem": "",
"x509_cert_sha1_thumbprint": "",
"user": "",
"password": ""
}
},
"batch": {
"account": "",
"account_key": "",

Просмотреть файл

@ -157,6 +157,20 @@ def derive_public_key_pem_from_pfx(pfxfile, passphrase=None, pemfile=None):
return pemfile
def _parse_sha1_thumbprint_openssl(output):
# type: (str) -> str
"""Get SHA1 thumbprint from buffer
:param str buffer: buffer to parse
:rtype: str
:return: sha1 thumbprint of buffer
"""
# return just thumbprint (without colons) from the above openssl command
# in lowercase. Expected openssl output is in the form:
# SHA1 Fingerprint=<thumbprint>
return ''.join(util.decode_string(
output).strip().split('=')[1].split(':')).lower()
def get_sha1_thumbprint_pfx(pfxfile, passphrase):
# type: (str, str) -> str
"""Get SHA1 thumbprint of PFX
@ -178,12 +192,21 @@ def get_sha1_thumbprint_pfx(pfxfile, passphrase):
['openssl', 'x509', '-noout', '-fingerprint'], stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
sha1_cert_tp = proc.communicate(input=pfxdump)[0]
# return just thumbprint (without colons) from the above openssl command
# in lowercase. Expected openssl output is in the form:
# SHA1 Fingerprint=<thumbprint>
return ''.join(util.decode_string(
sha1_cert_tp).strip().split('=')[1].split(':')).lower()
return _parse_sha1_thumbprint_openssl(proc.communicate(input=pfxdump)[0])
def get_sha1_thumbprint_pem(pemfile):
# type: (str) -> str
"""Get SHA1 thumbprint of PEM
:param str pfxfile: name of the pfx file to export
:rtype: str
:return: sha1 thumbprint of pem
"""
proc = subprocess.Popen(
['openssl', 'x509', '-noout', '-fingerprint', '-in', pemfile],
stdout=subprocess.PIPE
)
return _parse_sha1_thumbprint_openssl(proc.communicate()[0])
def generate_pem_pfx_certificates(config):

Просмотреть файл

@ -49,6 +49,7 @@ import azure.batch.models as batchmodels
from . import batch
from . import crypto
from . import data
from . import keyvault
from . import settings
from . import storage
from . import util
@ -206,6 +207,34 @@ def _create_clients(config):
return batch_client, blob_client, queue_client, table_client
def create_keyvault_client(ctx, config):
# type: (CliContext, dict) -> azure.keyvault.KeyVaultClient
"""Create KeyVault client
:param CliContext ctx: Cli Context
:param dict config: configuration dict
:rtype: azure.keyvault.KeyVaultClient
:return: key vault client
"""
kv = settings.credentials_keyvault(config)
aad_directory_id = ctx.aad_directory_id or kv.aad_directory_id
aad_application_id = ctx.aad_application_id or kv.aad_application_id
aad_auth_key = ctx.aad_auth_key or kv.aad_auth_key
aad_user = ctx.aad_user or kv.aad_user
aad_password = ctx.aad_password or kv.aad_password
aad_cert_private_key = ctx.aad_cert_private_key or kv.aad_cert_private_key
aad_cert_thumbprint = ctx.aad_cert_thumbprint or kv.aad_cert_thumbprint
# check if no keyvault/aad params were specified at all
if (aad_directory_id is None and aad_application_id is None and
aad_auth_key is None and aad_user is None and
aad_password is None and aad_cert_private_key is None and
aad_cert_thumbprint is None):
return None
else:
return keyvault.create_client(
aad_directory_id, aad_application_id, aad_auth_key, aad_user,
aad_password, aad_cert_private_key, aad_cert_thumbprint)
def initialize(config):
# type: (dict) -> tuple
"""Initialize fleet and create authenticated clients
@ -218,6 +247,35 @@ def initialize(config):
return _create_clients(config)
def fetch_credentials_json_from_keyvault(
keyvault_client, keyvault_uri, keyvault_credentials_secret_id):
# type: (azure.keyvault.KeyVaultClient, str, str) -> dict
"""Fetch a credentials json from keyvault
:param azure.keyvault.KeyVaultClient keyvault_client: keyvault client
:param str keyvault_uri: keyvault uri
:param str keyvault_credentials_secret_id: keyvault cred secret id
:rtype: dict
:return: credentials json
"""
if keyvault_uri is None:
raise ValueError('credentials json was not specified or is invalid')
if keyvault_client is None:
raise ValueError('no Azure KeyVault or AAD credentials specified')
return keyvault.fetch_credentials_json(
keyvault_client, keyvault_uri, keyvault_credentials_secret_id)
def fetch_secrets_from_keyvault(keyvault_client, config):
# type: (azure.keyvault.KeyVaultClient, dict) -> None
"""Fetch secrets with secret ids in config from keyvault
:param azure.keyvault.KeyVaultClient keyvault_client: keyvault client
:param dict config: configuration dict
"""
if keyvault_client is None:
raise ValueError('no Azure KeyVault or AAD credentials specified')
keyvault.parse_secret_ids(keyvault_client, config)
def _setup_nvidia_driver_package(blob_client, config, vm_size):
# type: (azure.storage.blob.BlockBlobService, dict, str) -> pathlib.Path
"""Set up the nvidia driver package
@ -1037,6 +1095,37 @@ def _adjust_settings_for_pool_creation(config):
pass
def action_keyvault_add(keyvault_client, config, keyvault_uri, name):
# type: (azure.keyvault.KeyVaultClient, dict, str, str) -> None
"""Action: Keyvault Add
:param azure.keyvault.KeyVaultClient keyvault_client: keyvault client
:param dict config: configuration dict
:param str keyvault_uri: keyvault uri
:param str name: secret name
"""
keyvault.store_credentials_json(
keyvault_client, config, keyvault_uri, name)
def action_keyvault_del(keyvault_client, keyvault_uri, name):
# type: (azure.keyvault.KeyVaultClient, str, str) -> None
"""Action: Keyvault Del
:param azure.keyvault.KeyVaultClient keyvault_client: keyvault client
:param str keyvault_uri: keyvault uri
:param str name: secret name
"""
keyvault.delete_secret(keyvault_client, keyvault_uri, name)
def action_keyvault_list(keyvault_client, keyvault_uri):
# type: (azure.keyvault.KeyVaultClient, str) -> None
"""Action: Keyvault List
:param azure.keyvault.KeyVaultClient keyvault_client: keyvault client
:param str keyvault_uri: keyvault uri
"""
keyvault.list_secrets(keyvault_client, keyvault_uri)
def action_cert_create(config):
# type: (dict) -> None
"""Action: Cert Create

Просмотреть файл

@ -34,9 +34,11 @@ import json
import logging
import zlib
# non-stdlib imports
import adal
import azure.common.credentials
import azure.keyvault
import azure.mgmt.resource.resources
import msrestazure.azure_active_directory
# local imports
from . import settings
from . import util
@ -52,8 +54,8 @@ _SECRET_ENCODED_FORMAT_VALUE = 'zlib+base64'
def _create_aad_credentials(
aad_directory_id, aad_application_id, aad_auth_key, aad_user,
aad_password):
# type: (str, str, str, str, str) ->
aad_password, aad_cert_private_key, aad_cert_thumbprint):
# type: (str, str, str, str, str, str, str) ->
# azure.common.credentials.ServicePrincipalCredentials or
# azure.common.credentials.UserPassCredentials
"""Create Azure Active Directory credentials
@ -62,46 +64,67 @@ def _create_aad_credentials(
:param str aad_auth_key: aad auth key
:param str aad_user: aad user
:param str aad_password: aad_password
:param str aad_cert_private_key: path to rsa private key
:param str aad_cert_thumbprint: sha1 thumbprint
:rtype: azure.common.credentials.ServicePrincipalCredentials or
azure.common.credentials.UserPassCredentials
:return: aad credentials object
"""
if aad_directory_id is not None and aad_user is not None:
raise ValueError(
'cannot specify both an AAD Service Principal and User')
if aad_directory_id is not None:
if aad_application_id is not None and aad_cert_private_key is not None:
if aad_auth_key is not None:
raise ValueError('cannot specify both cert auth and auth key')
if aad_password is not None:
raise ValueError('cannot specify both cert auth and password')
context = adal.AuthenticationContext(
'https://login.microsoftonline.com/{}'.format(aad_directory_id))
return msrestazure.azure_active_directory.AdalAuthentication(
lambda: context.acquire_token_with_client_certificate(
_KEYVAULT_RESOURCE,
aad_application_id,
util.decode_string(open(aad_cert_private_key, 'rb').read()),
aad_cert_thumbprint
)
)
elif aad_auth_key is not None:
if aad_password is not None:
raise ValueError(
'cannot specify both an AAD Service Principal and User')
return azure.common.credentials.ServicePrincipalCredentials(
aad_application_id,
aad_auth_key,
tenant=aad_directory_id,
resource=_KEYVAULT_RESOURCE,
)
elif aad_user is not None:
elif aad_password is not None:
return azure.common.credentials.UserPassCredentials(
username=aad_user,
password=aad_password,
resource=_KEYVAULT_RESOURCE,
)
else:
raise ValueError('neither AAD Service Principal or User specified')
raise ValueError(
'AAD Service Principal, User or Certificate not specified')
def create_client(
aad_directory_id, aad_application_id, aad_auth_key, aad_user,
aad_password):
# type: (str, str, str, str, str) -> azure.keyvault.KeyVaultClient
aad_password, aad_cert_private_key, aad_cert_thumbprint):
# type: (str, str, str, str, str, str, str) ->
# azure.keyvault.KeyVaultClient
"""Create KeyVault client
:param str aad_directory_id: aad directory/tenant id
:param str aad_application_id: aad application/client id
:param str aad_auth_key: aad auth key
:param str aad_user: aad user
:param str aad_password: aad_password
:param str aad_cert_private_key: path to rsa private key
:param str aad_cert_thumbprint: sha1 thumbprint
:rtype: azure.keyvault.KeyVaultClient
:return: keyvault client
"""
credentials = _create_aad_credentials(
aad_directory_id, aad_application_id, aad_auth_key, aad_user,
aad_password)
aad_password, aad_cert_private_key, aad_cert_thumbprint)
return azure.keyvault.KeyVaultClient(credentials)
@ -145,7 +168,9 @@ def store_credentials_json(client, config, keyvault_uri, secret_name):
:param str keyvault_uri: keyvault uri
:param str secret_name: secret name for creds json
"""
creds = {'credentials': settings.raw_credentials(config)}
creds = {
'credentials': settings.raw_credentials(config, True)
}
creds = json.dumps(creds).encode('utf8')
# first zlib compress and encode as base64
encoded = util.base64_encode_string(zlib.compress(creds))

Просмотреть файл

@ -146,6 +146,13 @@ ResourceFileSettings = collections.namedtuple(
'file_path', 'blob_source', 'file_mode',
]
)
KeyVaultSettings = collections.namedtuple(
'KeyVaultSettings', [
'keyvault_uri', 'keyvault_credentials_secret_id', 'aad_directory_id',
'aad_application_id', 'aad_auth_key', 'aad_user', 'aad_password',
'aad_cert_private_key', 'aad_cert_thumbprint',
]
)
def get_gluster_volume():
@ -480,14 +487,96 @@ def pool_sku(config, lower=False):
# CREDENTIALS SETTINGS
def raw_credentials(config):
# type: (dict) -> dict
def raw_credentials(config, omit_keyvault):
# type: (dict, bool) -> dict
"""Get raw credentials dictionary
:param dict config: configuration object
:param bool omit_keyvault: omit keyvault settings if present
:rtype: dict
:return: credentials dict
"""
return config['credentials']
conf = config['credentials']
if omit_keyvault:
conf.pop('keyvault', None)
return conf
def credentials_keyvault(config):
# type: (dict) -> KeyVaultSettings
"""Get KeyVault settings
:param dict config: configuration object
:rtype: KeyVaultSettings
:return: Key Vault settings
"""
try:
conf = config['credentials']['keyvault']
except (KeyError, TypeError):
conf = {}
try:
keyvault_uri = conf['uri']
if util.is_none_or_empty(keyvault_uri):
raise KeyError()
except KeyError:
keyvault_uri = None
try:
keyvault_credentials_secret_id = conf['credentials_secret_id']
if util.is_none_or_empty(keyvault_credentials_secret_id):
raise KeyError()
except KeyError:
keyvault_credentials_secret_id = None
try:
aad_directory_id = conf['aad']['directory_id']
if util.is_none_or_empty(aad_directory_id):
raise KeyError()
except KeyError:
aad_directory_id = None
try:
aad_application_id = conf['aad']['application_id']
if util.is_none_or_empty(aad_application_id):
raise KeyError()
except KeyError:
aad_application_id = None
try:
aad_auth_key = conf['aad']['auth_key']
if util.is_none_or_empty(aad_auth_key):
raise KeyError()
except KeyError:
aad_auth_key = None
try:
aad_user = conf['aad']['user']
if util.is_none_or_empty(aad_user):
raise KeyError()
except KeyError:
aad_user = None
try:
aad_password = conf['aad']['password']
if util.is_none_or_empty(aad_password):
raise KeyError()
except KeyError:
aad_password = None
try:
aad_cert_private_key = conf['aad']['rsa_private_key_pem']
if util.is_none_or_empty(aad_cert_private_key):
raise KeyError()
except KeyError:
aad_cert_private_key = None
try:
aad_cert_thumbprint = conf['aad']['x509_cert_sha1_thumbprint']
if util.is_none_or_empty(aad_cert_thumbprint):
raise KeyError()
except KeyError:
aad_cert_thumbprint = None
return KeyVaultSettings(
keyvault_uri=keyvault_uri,
keyvault_credentials_secret_id=keyvault_credentials_secret_id,
aad_directory_id=aad_directory_id,
aad_application_id=aad_application_id,
aad_auth_key=aad_auth_key,
aad_user=aad_user,
aad_password=aad_password,
aad_cert_private_key=aad_cert_private_key,
aad_cert_thumbprint=aad_cert_thumbprint,
)
def credentials_batch(config):

Просмотреть файл

@ -29,6 +29,19 @@ The credentials schema is as follows:
```json
{
"credentials": {
"keyvault": {
"uri": "https://myvault.vault.azure.net/",
"credentials_secret_id": "https://myvault.vault.azure.net/secrets/credentialsjson",
"aad": {
"directory_id": "01234567-89ab-cdef-0123-456789abcdef",
"application_id": "01234567-89ab-cdef-0123-456789abcdef",
"auth_key": "01234...",
"rsa_private_key_pem": "/path/to/privkey.pem",
"x509_cert_sha1_thumbprint": "01AB02CD...",
"user": "me@domain.com",
"password": "password"
}
},
"batch": {
"account": "awesomebatchaccountname",
"account_key": "batchaccountkey",
@ -61,6 +74,31 @@ The credentials schema is as follows:
The `credentials` property is where Azure Batch and Storage credentials
are defined.
* (optional) The `keyvault` property defines the required members for
accessing Azure KeyVault with Azure Active Directory credentials. Note that
this property is *mutually exclusive* of all other properties in this file.
If you need to define other members in this config file while using Azure
KeyVault, then you will need to use environment variables or cli parameters
instead for AAD and KeyVault credentials.
* (optional) `uri` property defines the Azure KeyVault DNS name (URI).
* (optional) `credentials_secret_id` property defines the KeyVault secret
id containing an entire credentials.json file.
* (optional) `aad` property contains members for Azure Active Directory
credentials. Note that some options are mutually exclusive of each other
depending upon authentication type: `auth_key`, `rsa_private_key_pem` and
`password` cannot be defined at the same time. Please see the
[Azure KeyVault and Batch Shipyard Guide](74-batch-shipyard-azure-keyvault.md)
for more information. Note that any of the following can be specified as
a CLI option or environment variable instead. For example, if you do not
want to store the `auth_key` in the file, it can be specified at runtime.
* (optional) `directory_id` AAD directory (tenant) id
* (optional) `application_id` AAD application (client) id
* (optional) `auth_key` Service Principal authentication key
* (optional) `rsa_private_key_pem` path to RSA private key PEM file
* (optional) `x509_cert_sha1_thumbprint` thumbprint of the X.509
certificate for use with Certificate-based authentication
* (optional) `user` AAD username
* (optional) `password` AAD password associated with the user
* (required) The `batch` property defines the Azure Batch account. Members
under the `batch` property can be found in the
[Azure Portal](https://portal.azure.com) under your Batch account.

Просмотреть файл

@ -30,7 +30,7 @@ is useful for managing sensitive data such as secrets.
The following setup sections will guide you through setting up Azure
Active Directory and KeyVault for use with Batch Shipyard.
### Azure Active Directory Setup
### Azure Active Directory Setup with Service Principal Key-based Authentication
If you will use your login for accessing your Azure subscription (e.g., your
login to Azure Portal loads your subscription) as credentials to access your
KeyVault in your subscription, then you can skip this section and proceed
@ -38,17 +38,24 @@ to the Azure KeyVault Setup below. If not, or if you prefer to use an Active
Directory Service Principal, then please continue following the directions
below.
If you prefer to use asymmetric X.509 certificate-based authentication, then
please see the next section. You can also augment the Key-based Authentication
with Certificate authentication by following the Certificate guide immediately
after the Key-based authentication guide.
#### Step 0: Get or Create a Directory
First, you will need to create a directory if you do not have any existing
directories (you should have a default directory) or if you are a user of a
directory that does not allow you to register an Application. You will need
to use the [Classic Azure Portal](https://manage.windowsazure.com/) to create
a directory.
a directory, however, it is recommended to use the default directory that
is associated with your subscription if possible.
#### Step 1: Retrieve the Directory ID
Retrieve the Directory ID of the Active Directory from the Azure Portal.
Click on `Azure Active Directory` on the left menu and then `Properties`. You
will see the `Directory ID` displayed on the next blade on the right.
Retrieve the Directory ID of the Active Directory from the
[Azure Portal](https://portal.azure.com). Click on `Azure Active Directory`
on the left menu and then `Properties`. You will see the `Directory ID`
displayed on the next blade on the right.
![74-aad-step1-0.png](https://azurebatchshipyard.blob.core.windows.net/github/74-aad-step1-0.png)
@ -67,13 +74,13 @@ the Create blade.
![74-aad-step2-0.png](https://azurebatchshipyard.blob.core.windows.net/github/74-aad-step2-0.png)
Ensure that the Application Type is set as `Native`. The `Redirect URI` does
not have to exist, you can fill in anything you'd like here. Once completed,
hit the Create button at the bottom.
Ensure that the Application Type is set as `Web app / API`. The `Redirect URI`
does not have to exist, you can fill in anything you'd like here. Once
completed, hit the `Create` button at the bottom.
#### Step 3: Retrieve the Application ID
Retrieve the `Application ID` for the application you just registered by
refreshing the App registrations blade. You will see the `Application ID`
refreshing the `App registrations` blade. You will see the `Application ID`
on the top right of the blade.
![74-aad-step3-0.png](https://azurebatchshipyard.blob.core.windows.net/github/74-aad-step3-0.png)
@ -94,9 +101,121 @@ blade (if not, click the `All settings ->` button). Click on `Keys` under
Add a description for the key and select an appropriate expiration interval.
Click on the `Save` button at the top left of the blade. After you save
the key, the key `VALUE` will be displayed. Copy this value. This is the
argument to pass to the option `--aad-auth-key` or set as the environment
variable `SHIPYARD_AAD_AUTH_KEY`.
the key, the key `VALUE` will be displayed. Copy this value and ensure this
value is stored somewhere safe. This is the argument to pass to the option
`--aad-auth-key` or set as the environment variable `SHIPYARD_AAD_AUTH_KEY`.
Once you navigate away from this blade, the value of the key cannot be
retrieved again.
### Azure Active Directory Setup with Service Principal Certificate-based Authentication
The following describes how to set up Azure Active Directory with asymmetric
X.509 certificate-based authentication. The
[Azure CLI](https://docs.microsoft.com/en-us/azure/xplat-cli-install) will
be used in this guide to perform setup. Other tools such as Azure PowerShell
can also be used.
#### Step 0: Login to Azure subscription and get Directory ID
Log in to your Azure subscription with Azure cli:
```shell
azure login
azure account set "<subscription name or id>"
```
Ensure that `azure` is running in ARM mode.
Retrieve the Directory ID with the following command:
```shell
azure account show
```
You will see a line starting with `Tenant ID`. This is the Directory ID.
This is the argument to pass to the option `--aad-directory-id` or set as
the environment variable `SHIPYARD_AAD_DIRECTORY_ID`.
#### Step 1: Create X.509 Certificate with Asymmetric Keys
Execute the following `openssl` command, replacing the `-days` and
`-subj` parameters with the appropriate values:
```shell
openssl req -x509 -days 3650 -newkey rsa:2048 -out cert.pem -nodes -subj '/CN=mykeyvault'
```
This command will create two files: `cert.pem` and `privkey.pem`. The
`cert.pem` file contains the X.509 certificate with public key. This
certificate will be attached to the Active Directory Application.
The `privkey.pem` file contains the RSA private key that will be used to
authenticate with Azure Active Directory for the Service Principal. This
file path is the argument to pass to the option `--aad-cert-private-key` or
set as the environment variable `SHIPYARD_AAD_CERT_PRIVATE_KEY`.
You will also need to get the SHA1 thumbprint of the certificate created.
This can be done with the following command:
```shell
openssl x509 -in cert.pem -fingerprint -noout | sed 's/SHA1 Fingerprint=//g' | sed 's/://g'
```
This value output is the argument to pass to the option
`--aad-cert-thumbprint` or set as the environment variable
`SHIPYARD_AAD_CERT_THUMBPRINT`.
#### Step 2: Create an Active Directory Application and Service Principal with Certificate
Execute the following command to create the AAD application and Service
Principal together with the Certificate data, replacing the `-n` value
with the name of the application as desired:
```shell
azure ad sp create -n mykeyvault --cert-value "$(tail -n+2 cert.pem | head -n-1 | tr -d '\n')"
```
You can specify an optional `--end-date` parameter to change the validity
period of the certificate.
This action will output something similar to the following:
```
data: Object Id: abcdef01-2345-6789-abcd-ef0123456789
data: Display Name: mykeyvault
data: Service Principal Names:
data: 01234567-89ab-cdef-0123-456789abcdef
data: http://mykeyvault
```
Note the ID under `Service Prinipal Names:`. This is the Application ID.
This is the argument to pass to the option `--aad-application-id` or set
as the environment variable `SHIPYARD_AAD_APPLICATION_ID`.
#### Step 3: Create a Role for the Service Principal
Execute the following command to create a role for the service principal.
You will need the `Object Id` as displayed in the previous step and your
Azure Subscription ID. Replace the `-o` role name with one of `Owner`,
`Contributor`, or `Reader` (if this Service Principal will only read
from KeyVault).
```shell
azure role assignment create --objectId abcdef01-2345-6789-abcd-ef0123456789 -o Contributor -c /subscriptions/11111111-2222-3333-4444-555555555555/
```
You should receive a message output that the role assignment has been
completed successfully.
You can now assign this Service Principal with Certificate-based
authentication to an Azure KeyVault as per instructions below.
#### [Optional] Set Certificate Data for Existing Service Principal
If you followed the prior section for creating a Service Principal with
Key-based authentication, you can augment the Service Principal with
Certificate-based authentication by executing the following command,
replacing the `-o` parameter with the Object Id of the AAD Application:
```shell
azure ad app set -o abcdef01-2345-6789-abcd-ef0123456789 --cert-value "$(tail -n+2 cert.pem | head -n-1 | tr -d '\n')"
```
You can specify an optional `--end-date` parameter to change the validity
period of the certificate.
### Azure KeyVault Setup
The following describes how to set up a new KeyVault for use with Batch
@ -116,7 +235,7 @@ the blade that follows.
![74-akv-step0-1.png](https://azurebatchshipyard.blob.core.windows.net/github/74-akv-step0-1.png)
#### Step 1: Create KeyVault
#### Step 1: Create the KeyVault
In the Create Key Vault blade, fill in the `Name`, select the `Subscription`
to use, set the `Resource Group` and `Location` of the KeyVault to be created.
@ -127,12 +246,12 @@ Service Principal, then skip to the next step, but follow the instructions for
the specific Active Directory User. If you are using an Active Directory
Service Principal, then hit the `Add new` button, then `Select principal`. In
the next blade, type the name of the Application added in the prior section
to Active Directory. Select this application by clicking on it and the Select
button on the bottom of the blade.
to Active Directory. Select this application by clicking on it and the
`Select` button on the bottom of the blade.
#### Step 2: Set Secret Permissions
From the `Configure from template (optional)` pulldown, select either
`Key & Secret Management` or `Secret Management`, then hit OK.
`Key & Secret Management` or `Secret Management`, then click the `OK` button.
![74-akv-step2-0.png](https://azurebatchshipyard.blob.core.windows.net/github/74-akv-step2-0.png)
@ -155,10 +274,27 @@ with Batch Shipyard with Azure KeyVault using Azure Active Directory
credentials.
### Authenticating with AAD and Azure KeyVault
You will need to provide *either* an AAD Service Principal or AAD
User/Password to authenticate for access to your KeyVault. These options
are mutually exclusive. For an AAD Service Principal, you would need to
provide the following options to your `shipyard` invocation:
You will need to provide one of the following:
1. an AAD Service Principal with an authentication key
2. an AAD Service Principal with a RSA private key and X.509 certificate
thumbprint
3. an AAD User/Password
in order to authenticate for access to your KeyVault. These options
are mutually exclusive.
You can either provide the required parameters through CLI options,
environment variables, or the `credentials.json` file.
Please see the [configuration guide](10-batch-shipyard-configuration.md) for
the appropriate json properties to populate that correlate with the following
options below. Note that the `keyvault add` command must use a
`credentials.json` file that does not have KeyVault and AAD credentials.
For this command, you will need to use CLI options or environment variables.
For an AAD Service Principal with Key-based authentication, you will need
to provide the following options to your `shipyard` invocation:
```
--aad-directory-id <DIRECTORY-ID> --aad-application-id <APPLICATION-ID> --aad-auth-key <AUTH-KEY>
@ -170,6 +306,27 @@ or as environment variables:
SHIPYARD_AAD_DIRECTORY_ID=<DIRECTORY-ID> SHIPYARD_AAD_APPLICATION_ID=<APPLICATION-ID> SHIPYARD_AAD_AUTH_KEY=<AUTH-KEY>
```
For an AAD Service Principal with Certificate-based authentication, you will
need to provide the following options to your `shipyard` invocation:
```
--aad-directory-id <DIRECTORY-ID> --aad-application-id <APPLICATION-ID> --aad-cert-private-key <RSA-PRIVATE-KEY-FILE> --aad-cert-thumbprint <CERT-SHA1-THUMBPRINT>
```
or as environment variables:
```
SHIPYARD_AAD_DIRECTORY_ID=<DIRECTORY-ID> SHIPYARD_AAD_APPLICATION_ID=<APPLICATION-ID> SHIPYARD_AAD_CERT_PRIVATE_KEY=<RSA-PRIVATE-KEY-FILE> SHIPYARD_AAD_CERT_THUMBPRINT=<CERT-SHA1-THUMBPRINT>
```
To retrieve the SHA1 thumbprint for the X.509 certificate (not the RSA
private key) associated with your Service Principal, you can run the
following `openssl` command against your certificate file:
```shell
openssl x509 -in cert.pem -fingerprint -noout | sed 's/SHA1 Fingerprint=//g' | sed 's/://g'
```
To use an AAD User/Password to authenticate for access to your KeyVault, you
would need to add the following options to your `shipyard` invocation.
@ -203,11 +360,16 @@ it is recommended to use the Batch Shipyard CLI to store your
and can allow for potentially very large credential files as compression
is applied to the file prior to placing the secret in KeyVault. To create
a credentials secret, pass all of the required AAD and KeyVault URI options
to `keyvault add`. For example:
to `keyvault add`. Note that you cannot use AAD/KeyVault credential options
in a `credentials.json` file to authenticate with KeyVault to store the
same json file. You must use CLI options or environment variables to pass
the appropriate Azure Keyvault and AAD credentials to `shipyard` for this
command. For example:
```shell
# add the appropriate AAD and KeyVault URI options or environment variables
# to the below invocation
# to the below invocation. These AAD/KeyVault authentication options cannot
# be present in credentials.json.
shipyard keyvault add mycreds --credentials credentials.json
```
@ -228,7 +390,9 @@ https://myvault.vault.azure.net/secrets/mycreds
```
This secret id is required for retrieving your `credentials.json` contents
for latter invocations that require valid credentials to use Batch Shipyard.
for later invocations that require these particular credentials to interact
with Batch Shipyard. How to pass this secret id will be explained in the next
section.
You can also store individual keys as secrets in your KeyVault and reference
them within your `credentials.json` file. For example:
@ -249,20 +413,23 @@ options to the invocation and then populate the `account_key` property from
the value of the secret.
These `*_keyvault_secret_id` properties can be used in lieu of batch account
keys, storage account keys, and private docker registry passwords. Please
see the [configuration guide](10-batch-shipyard-configuration.md) for more
keys, storage account keys, and private Docker Registry passwords. You will
need to populate the associated KeyVault with these secrets manually and
set the json properties in the `credentials.json` file with the corresponding
secret ids. Please see the
[configuration guide](10-batch-shipyard-configuration.md) for more
information.
Finally, Batch Shipyard does support nested KeyVault secrets. In other words,
the `credentials.json` file can be a secret in KeyVault and there can be
`*_keyvault_secret_id` properties within the json file stored in KeyVault
which will then be fetched.
which subsequently will be fetched automatically.
### Fetching Credentials from KeyVault
To specify a `credentials.json` as a secret in KeyVault, you can omit the
`--credentials` option (or specify a `--configdir` option without a
`credentials.json` file in the path pointed to by `--configdir`) and instead
specify the option:
`credentials.json` file on disk in the path pointed to by `--configdir`) and
instead specify the option:
```
--keyvault-credentials-secret-id <SECRET-ID>
@ -274,14 +441,29 @@ SHIPYARD_KEYVAULT_CREDENTIALS_SECRET_ID=<SECRET-ID>
```
If you have a physical `credentials.json` file on disk, but with
`*_keyvault_secret_id` properties then you do not need to specify the above
`*_keyvault_secret_id` properties then you must not specify the above
option as Batch Shipyard will parse the credentials file and perform
the lookup and secret retrieval.
the lookup and secret retrieval automatically.
## Configuration Documentation
Please see [this page](10-batch-shipyard-configuration.md) for a full
explanation of each configuration option.
## More Documentation
You can perform many Azure Active Directory setup steps through the Azure CLI.
This [document](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal-cli)
explains how to create a Service Principal among other topics. To create
an Azure KeyVault with the Azure CLI, this
[document](https://docs.microsoft.com/en-us/azure/key-vault/key-vault-manage-with-cli#register-an-application-with-azure-active-directory)
explains the steps involved. Additionally, it shows how to authorize an AAD
Application for use with KeyVault.
## Usage Documentation
Please see [this page](20-batch-shipyard-usage.md) for a full
Securing your KeyVault can be handled through the Azure Portal or through
the Azure CLI. This
[document](https://docs.microsoft.com/en-us/azure/key-vault/key-vault-secure-your-key-vault)
explains concepts and how to secure your KeyVault. A general overview of
Role-Based Access Control (RBAC) and how to manage access to various
resources can be found in this
[document](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-control-what-is).
For further Batch Shipyard documentation, please see
[this page](10-batch-shipyard-configuration.md) for a full
explanation of each property in the configuration files. Please see
[this page](20-batch-shipyard-usage.md) for a full
explanation of all commands and options for `shipyard`.

Просмотреть файл

@ -13,6 +13,8 @@ current limitation of the underlying Azure Batch service.
This is a current limitation of the underlying VM and host drivers.
* On-premise Docker private registries are not supported at this time due to
VNet requirements.
* Credential storage using Azure Active Directory and KeyVault is only
supported for Azure Public Cloud regions.
The following Azure Batch actions should only be performed through Batch
Shipyard when deploying your workload through this toolkit, as Batch

Просмотреть файл

@ -1,8 +1,10 @@
adal==0.4.3
azure-batch==1.1.0
azure-keyvault==0.1.0
azure-storage==0.33.0
blobxfer==0.12.1
click==6.6
future==0.16.0
msrestazure==0.4.6
pathlib2==2.1.0; python_version < '3.5'
scandir==1.4; python_version < '3.5'

Просмотреть файл

@ -39,8 +39,8 @@ except ImportError:
# non-stdlib imports
import click
# local imports
import convoy.keyvault
import convoy.fleet
import convoy.settings
import convoy.util
# create logger
@ -69,6 +69,8 @@ class CliContext(object):
self.aad_auth_key = None
self.aad_user = None
self.aad_password = None
self.aad_cert_private_key = None
self.add_cert_thumbprint = None
def initialize(self, creds_only=False, no_config=False):
# type: (CliContext, bool, bool) -> None
@ -77,17 +79,20 @@ class CliContext(object):
:param bool creds_only: credentials only initialization
:param bool no_config: do not configure context
"""
self.keyvault_client = convoy.keyvault.create_client(
self.aad_directory_id, self.aad_application_id,
self.aad_auth_key, self.aad_user, self.aad_password)
self._read_credentials_config()
self.keyvault_client = convoy.fleet.create_keyvault_client(
self, self.config)
del self.aad_directory_id
del self.aad_application_id
del self.aad_auth_key
del self.aad_user
del self.aad_password
del self.aad_cert_private_key
del self.aad_cert_thumbprint
self.config = None
self._init_config(creds_only)
if no_config:
return
self._init_config(creds_only)
if not creds_only:
clients = convoy.fleet.initialize(self.config)
self._set_clients(*clients)
@ -112,6 +117,21 @@ class CliContext(object):
'is valid and is encoded UTF-8 without BOM.'.format(
json_file)))
def _read_credentials_config(self):
# type: (CliContext) -> None
"""Read credentials config file only
:param CliContext self: this
"""
# use configdir if available
if self.configdir is not None and self.json_credentials is None:
self.json_credentials = pathlib.Path(
self.configdir, 'credentials.json')
if (self.json_credentials is not None and
not isinstance(self.json_credentials, pathlib.Path)):
self.json_credentials = pathlib.Path(self.json_credentials)
if self.json_credentials.exists():
self._read_json_file(self.json_credentials)
def _init_config(self, creds_only=False):
# type: (CliContext, bool) -> None
"""Initializes configuration of the context
@ -134,15 +154,6 @@ class CliContext(object):
if (self.json_credentials is not None and
not isinstance(self.json_credentials, pathlib.Path)):
self.json_credentials = pathlib.Path(self.json_credentials)
kvcreds = None
if self.json_credentials is None or not self.json_credentials.exists():
if self.keyvault_uri is None:
raise ValueError('credentials json was not specified')
else:
# fetch credentials from keyvault
kvcreds = convoy.keyvault.fetch_credentials_json(
self.keyvault_client, self.keyvault_uri,
self.keyvault_credentials_secret_id)
if not creds_only:
if self.json_config is None:
raise ValueError('config json was not specified')
@ -152,14 +163,40 @@ class CliContext(object):
raise ValueError('pool json was not specified')
elif not isinstance(self.json_pool, pathlib.Path):
self.json_pool = pathlib.Path(self.json_pool)
# load json files into memory
# fetch credentials from keyvault, if json file is missing
kvcreds = None
if self.json_credentials is None or not self.json_credentials.exists():
kvcreds = convoy.fleet.fetch_credentials_json_from_keyvault(
self.keyvault_client, self.keyvault_uri,
self.keyvault_credentials_secret_id)
# read credentials json, perform special keyvault processing if
# required sections are missing
if kvcreds is None:
self._read_json_file(self.json_credentials)
kv = convoy.settings.credentials_keyvault(self.config)
self.keyvault_uri = self.keyvault_uri or kv.keyvault_uri
self.keyvault_credentials_secret_id = (
self.keyvault_credentials_secret_id or
kv.keyvault_credentials_secret_id
)
try:
convoy.settings.credentials_batch(self.config)
if len(list(convoy.settings.iterate_storage_credentials(
self.config))) == 0:
raise KeyError()
except KeyError:
# fetch credentials from keyvault
self.config = \
convoy.fleet.fetch_credentials_json_from_keyvault(
self.keyvault_client, self.keyvault_uri,
self.keyvault_credentials_secret_id)
else:
self.config = kvcreds
del kvcreds
del self.keyvault_credentials_secret_id
# parse any keyvault secret ids from credentials
convoy.keyvault.parse_secret_ids(self.keyvault_client, self.config)
convoy.fleet.fetch_secrets_from_keyvault(
self.keyvault_client, self.config)
# read rest of config files
if not creds_only:
self._read_json_file(self.json_config)
@ -312,6 +349,32 @@ def _aad_password_option(f):
callback=callback)(f)
def _aad_cert_private_key_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_cert_private_key = value
return value
return click.option(
'--aad-cert-private-key',
expose_value=False,
envvar='SHIPYARD_AAD_CERT_PRIVATE_KEY',
help='Azure Active Directory private key certificate',
callback=callback)(f)
def _aad_cert_thumbprint_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_cert_thumbprint = value
return value
return click.option(
'--aad-cert-thumbprint',
expose_value=False,
envvar='SHIPYARD_AAD_CERT_THUMBPRINT',
help='Azure Active Directory certificate SHA1 thumbprint',
callback=callback)(f)
def _configdir_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
@ -381,6 +444,8 @@ def _jobs_option(f):
def common_options(f):
f = _aad_cert_thumbprint_option(f)
f = _aad_cert_private_key_option(f)
f = _aad_password_option(f)
f = _aad_user_option(f)
f = _aad_auth_key_option(f)
@ -447,7 +512,7 @@ def keyvault(ctx):
def keyvault_add(ctx, name):
"""Add a credentials json as a secret to Azure KeyVault"""
ctx.initialize(creds_only=True)
convoy.keyvault.store_credentials_json(
convoy.fleet.action_keyvault_add(
ctx.keyvault_client, ctx.config, ctx.keyvault_uri, name)
@ -457,8 +522,8 @@ def keyvault_add(ctx, name):
@pass_cli_context
def keyvault_del(ctx, name):
"""Delete a secret from Azure KeyVault"""
ctx.initialize(no_config=True)
convoy.keyvault.delete_secret(
ctx.initialize(creds_only=True, no_config=True)
convoy.fleet.action_keyvault_del(
ctx.keyvault_client, ctx.keyvault_uri, name)
@ -467,8 +532,8 @@ def keyvault_del(ctx, name):
@pass_cli_context
def keyvault_list(ctx):
"""List secret ids and metadata in an Azure KeyVault"""
ctx.initialize(no_config=True)
convoy.keyvault.list_secrets(ctx.keyvault_client, ctx.keyvault_uri)
ctx.initialize(creds_only=True, no_config=True)
convoy.fleet.action_keyvault_list(ctx.keyvault_client, ctx.keyvault_uri)
@cli.group()