Skip to content
This repository was archived by the owner on Apr 7, 2022. It is now read-only.

[NOTEST][WIP]initial commit with vault+sprout integration via dynaconf #9402

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sprout/appliances/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from cfme.utils.appliance import Appliance as CFMEAppliance, IPAppliance
from cfme.utils.bz import Bugzilla
from cfme.utils.conf import cfme_data
from cfme.utils.providers import get_mgmt
from sprout.appliances.utils.providers import get_mgmt
from cfme.utils.timeutil import nice_seconds
from cfme.utils.version import Version

Expand Down
Empty file.
71 changes: 71 additions & 0 deletions sprout/appliances/utils/providers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from collections import Mapping

import six

from cfme.common.provider import all_types
from cfme.exceptions import UnknownProviderType
from cfme.utils import conf
from cfme.utils.log import logger
from sprout.vault.vault import settings

providers_data = conf.cfme_data.get("management_systems", {})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sprout is deployed along with test framework. do we really need to add it's own providers and vault files ?


PROVIDER_MGMT_CACHE = {}


def get_class_from_type(prov_type):
try:
return all_types()[prov_type]
except KeyError:
raise UnknownProviderType("Unknown provider type: {}!".format(prov_type))


def get_mgmt(provider_key, providers=None, credentials=None):
""" Provides a ``wrapanapi`` object, based on the request.

Args:
provider_key: The name of a provider, as supplied in the yaml configuration files.
You can also use the dictionary if you want to pass the provider data directly.
providers: A set of data in the same format as the ``management_systems`` section in the
configuration yamls. If ``None`` then the configuration is loaded from the default
locations. Expects a dict.
credentials: A set of credentials in the same format as the ``credentials`` yamls files.
If ``None`` then credentials are loaded from the vault using dynaconf. Expects a dict.
Return: A provider instance of the appropriate ``wrapanapi.WrapanapiAPIBase``
subclass
"""
if providers is None:
providers = providers_data
# provider_key can also be provider_data for some reason
# TODO rename the parameter; might break things
if isinstance(provider_key, Mapping):
provider_data = provider_key
provider_key = provider_data['name']
else:
provider_data = providers[provider_key]

if credentials is None:
# create env matching provider_keys in vault to hold credentials
with settings.using_env(provider_key):
credentials = {key.lower(): val for key, val in settings.as_dict().items()
if 'VAULT' not in key}

# Munge together provider dict and creds,
# Let the provider do whatever they need with them
provider_kwargs = provider_data.copy()
provider_kwargs.update(credentials)

if not provider_kwargs.get('username') and provider_kwargs.get('principal'):
provider_kwargs['username'] = provider_kwargs['principal']
provider_kwargs['password'] = provider_kwargs['secret']

if isinstance(provider_key, six.string_types):
provider_kwargs['provider_key'] = provider_key
provider_kwargs['logger'] = logger

if provider_key not in PROVIDER_MGMT_CACHE:
mgmt_instance = get_class_from_type(provider_data['type']).mgmt_class(**provider_kwargs)
PROVIDER_MGMT_CACHE[provider_key] = mgmt_instance
else:
logger.debug("returning cached mgmt class for '%s'", provider_key)
return PROVIDER_MGMT_CACHE[provider_key]
2 changes: 1 addition & 1 deletion sprout/appliances/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

from sprout.log import create_logger
from cfme.utils.bz import Bugzilla
from cfme.utils.providers import get_mgmt
from sprout.appliances.utils.providers import get_mgmt
from cfme.utils.version import Version

from wrapanapi import Openshift
Expand Down
1 change: 1 addition & 0 deletions sprout/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Django<1.10
celery[redis]<3.2
django-object-actions
django-ipware
dynaconf[vault]
flower
gunicorn
pika
Expand Down
Empty file added sprout/vault/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions sprout/vault/approle_setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
# This file briefly describes the steps and can execute those as well - to create Approle Token.
# make sure to have exported VAULT_ADDR=https://addr:port
# and VAULT_SKIP_VERIFY=true to disable ssl verification

echo 'login with kerberos - make sure you are admin by reading listed policies'
vault login -method=ldap -tls-skip-verify=true username=<user>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it ask for this value or look for it in env variables ?


# =======================================================================================
# NOTE: we do not need to run this every time, but only when you need a new AppRole Token.
echo 'write policy'
vault policy write cfme-qe-infra-ro-policy cfme-qe-infra-ro-policy.json

echo 'enable AppRole auth'
vault auth enable approle

echo 'create an AppRole called'
vault write auth/approle/role/cfme-qe-infra secret_id_ttl=10m secret_id_num_uses=0 token_num_uses=20 token_ttl=30m token_max_ttl=60m policies=cfme-qe-infra-ro-policy

echo 'Creating a Limited-Use Token'
vault policy write cfme-qe-infra-approle-token cfme-qe-infra-approle-token.json
vault token create -policy=cfme-qe-infra-approle-token
# =======================================================================================

echo 'Set following env variable.'
echo 'export VAULT_ENABLED_FOR_DYNACONF=true'
echo 'export VAULT_URL_FOR_DYNACONF=https://infra-assets.cfme2.lab.eng.rdu2.redhat.com:8201'
echo 'export VAULT_APPROLE_TOKEN=<token generated in previous step>'
echo 'export VAULT_VERIFY_FOR_DYNACONF=false'
10 changes: 10 additions & 0 deletions sprout/vault/cfme-qe-infra-approle-token.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"path": {
"auth/approle/role/cfme-qe-infra/role-id": {
"capabilities": ["read"]
},
"auth/approle/role/cfme-qe-infra/secret-id": {
"capabilities": ["create", "update"]
}
}
}
90 changes: 90 additions & 0 deletions sprout/vault/cfme-qe-infra-ro-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"path": {
"secret/*": {
"capabilities": [
"list"
]
},
"secret/cfme-qe-sprout/*": {
"capabilities": [
"read",
"list"
]
},
"secret/data/cfme-qe-sprout/*": {
"capabilities": [
"read",
"list"
]
},
"auth/token/lookup-self": {
"capabilities": [
"read"
]
},
"auth/token/renew-self": {
"capabilities": [
"update"
]
},
"auth/token/revoke-self": {
"capabilities": [
"update"
]
},
"sys/capabilities-self": {
"capabilities": [
"update"
]
},
"sys/renew": {
"capabilities": [
"update"
]
},
"sys/leases/renew": {
"capabilities": [
"update"
]
},
"sys/leases/lookup": {
"capabilities": [
"update"
]
},
"cubbyhole/*": {
"capabilities": [
"create",
"read",
"update",
"delete",
"list"
]
},
"sys/wrapping/wrap": {
"capabilities": [
"update"
]
},
"sys/wrapping/lookup": {
"capabilities": [
"update"
]
},
"sys/wrapping/unwrap": {
"capabilities": [
"update"
]
},
"sys/mounts": {
"capabilities": [
"read"
]
},
"sys/auth": {
"capabilities": [
"read"
]
}
}
}
53 changes: 53 additions & 0 deletions sprout/vault/vault.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import os

from dynaconf import LazySettings
from hvac import Client

VAULT_APPROLE = 'cfme-qe-infra'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd turn all this stuff into class

extra_dynaconf_args = {}

# The process to authenticate is to basically create AppRole Token and use that to authenticate
# with vault and every time you authenticate also renew the token. That token will allow you
# to create role_id and secret_id which together lets you authenticate to AppRole
vault_approle_token = os.environ.get("VAULT_APPROLE_TOKEN", None)
vault_url = os.environ.get("VAULT_URL_FOR_DYNACONF", None)


def _login_and_renew_token(url, token):
"""Log into Vault, renew the token, and return the Vault client"""
vault = Client(url=url, token=token, verify=False)
if not vault.is_authenticated():
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it login in this case ?

# Renew the token so that it's valid for another 7 days
vault.renew_token()
return vault


def _get_approle_ids(url, token):
vault = _login_and_renew_token(url, token)
if not vault:
return None
role_id = vault.get_role_id(VAULT_APPROLE)
secret_id = vault.create_role_secret_id(VAULT_APPROLE).get("data", {}).get("secret_id")
return {"role_id": role_id, "secret_id": secret_id}


if vault_approle_token and vault_url:
# Generate secret id
vault_approle_ids = _get_approle_ids(vault_url, vault_approle_token)
if not vault_approle_ids:
raise Exception(f"Cannot auth with Vault with AppRole token '{vault_approle_token}'")
extra_dynaconf_args.update(
{
"VAULT_ROLE_ID": vault_approle_ids["role_id"],
"VAULT_ROLE_ID_FOR_DYNACONF": vault_approle_ids["role_id"], # Jenkins vault
"VAULT_SECRET_ID": vault_approle_ids["secret_id"],
"VAULT_SECRET_ID_FOR_DYNACONF": vault_approle_ids["secret_id"], # Jenkins vault
}
)
settings = LazySettings(
VAULT_PATH_FOR_DYNACONF="cfme-qe-sprout",
VAULT_VERIFY_FOR_DYNACONF=False,
VAULT_ENABLED_FOR_DYNACONF=True,
**extra_dynaconf_args
)