Codebase list python-castellan / bc7f7a4
vault: add AppRole support Add support for use of AppRole's for authentication to Vault; this feature provides a more application centric approach to managing long term access to Vault. The functional tests exercise this integration with a restricted policy which only allows access to the default 'secret' backend. Change-Id: I59dfe31adb72712c53d49f66d9ac894e43e8bbad Closes-Bug: 1796851 James Page 5 years ago
4 changed file(s) with 183 addition(s) and 2 deletion(s). Raw diff Collapse all Expand all
2828 from keystoneauth1 import loading
2929 from oslo_config import cfg
3030 from oslo_log import log as logging
31 from oslo_utils import timeutils
3132 import requests
3233 import six
3334
4647 vault_opts = [
4748 cfg.StrOpt('root_token_id',
4849 help='root token for vault'),
50 cfg.StrOpt('approle_role_id',
51 help='AppRole role_id for authentication with vault'),
52 cfg.StrOpt('approle_secret_id',
53 help='AppRole secret_id for authentication with vault'),
4954 cfg.StrOpt('vault_url',
5055 default=DEFAULT_VAULT_URL,
5156 help='Use this endpoint to connect to Vault, for example: '
8792 loading.register_session_conf_options(self._conf, VAULT_OPT_GROUP)
8893 self._session = requests.Session()
8994 self._root_token_id = self._conf.vault.root_token_id
95 self._approle_role_id = self._conf.vault.approle_role_id
96 self._approle_secret_id = self._conf.vault.approle_secret_id
97 self._cached_approle_token_id = None
98 self._approle_token_ttl = None
99 self._approle_token_issue = None
90100 self._vault_url = self._conf.vault.vault_url
91101 if self._vault_url.startswith("https://"):
92102 self._verify_server = self._conf.vault.ssl_ca_crt_file or True
123133
124134 key_id if key_id else '?list=true')
125135
136 @property
137 def _approle_token_id(self):
138 if (all((self._approle_token_issue, self._approle_token_ttl)) and
139 timeutils.is_older_than(self._approle_token_issue,
140 self._approle_token_ttl)):
141 self._cached_approle_token_id = None
142 return self._cached_approle_token_id
143
144 def _build_auth_headers(self):
145 if self._root_token_id:
146 return {'X-Vault-Token': self._root_token_id}
147
148 if self._approle_token_id:
149 return {'X-Vault-Token': self._approle_token_id}
150
151 if self._approle_role_id:
152 params = {
153 'role_id': self._approle_role_id
154 }
155 if self._approle_secret_id:
156 params['secret_id'] = self._approle_secret_id
157 approle_login_url = '{}v1/auth/approle/login'.format(
158 self._get_url()
159 )
160 token_issue_utc = timeutils.utcnow()
161 try:
162 resp = self._session.post(url=approle_login_url,
163 json=params,
164 verify=self._verify_server)
165 except requests.exceptions.Timeout as ex:
166 raise exception.KeyManagerError(six.text_type(ex))
167 except requests.exceptions.ConnectionError as ex:
168 raise exception.KeyManagerError(six.text_type(ex))
169 except Exception as ex:
170 raise exception.KeyManagerError(six.text_type(ex))
171
172 if resp.status_code in _EXCEPTIONS_BY_CODE:
173 raise exception.KeyManagerError(resp.reason)
174 if resp.status_code == requests.codes['forbidden']:
175 raise exception.Forbidden()
176
177 resp = resp.json()
178 self._cached_approle_token_id = resp['auth']['client_token']
179 self._approle_token_issue = token_issue_utc
180 self._approle_token_ttl = resp['auth']['lease_duration']
181 return {'X-Vault-Token': self._approle_token_id}
182
183 return {}
184
126185 def _do_http_request(self, method, resource, json=None):
127186 verify = self._verify_server
128 headers = {'X-Vault-Token': self._root_token_id}
187 headers = self._build_auth_headers()
129188
130189 try:
131190 resp = method(resource, headers=headers, json=json, verify=verify)
3838 def set_defaults(conf, backend=None, barbican_endpoint=None,
3939 barbican_api_version=None, auth_endpoint=None,
4040 retry_delay=None, number_of_retries=None, verify_ssl=None,
41 api_class=None, vault_root_token_id=None, vault_url=None,
41 api_class=None, vault_root_token_id=None,
42 vault_approle_role_id=None, vault_approle_secret_id=None,
43 vault_url=None,
4244 vault_ssl_ca_crt_file=None, vault_use_ssl=None,
4345 barbican_endpoint_type=None):
4446 """Set defaults for configuration values.
5355 :param number_of_retries: Use this attribute to set number of retries.
5456 :param verify_ssl: Use this to specify if ssl should be verified.
5557 :param vault_root_token_id: Use this for the root token id for vault.
58 :param vault_approle_role_id: Use this for the approle role_id for vault.
59 :param vault_approle_secret_id: Use this for the approle secret_id
60 for vault.
5661 :param vault_url: Use this for the url for vault.
5762 :param vault_use_ssl: Use this to force vault driver to use ssl.
5863 :param vault_ssl_ca_crt_file: Use this for the CA file for vault.
96101 if vkm is not None:
97102 if vault_root_token_id is not None:
98103 conf.set_default('root_token_id', vault_root_token_id,
104 group=vkm.VAULT_OPT_GROUP)
105 if vault_approle_role_id is not None:
106 conf.set_default('approle_role_id', vault_approle_role_id,
107 group=vkm.VAULT_OPT_GROUP)
108 if vault_approle_secret_id is not None:
109 conf.set_default('approle_secret_id', vault_approle_secret_id,
99110 group=vkm.VAULT_OPT_GROUP)
100111 if vault_url is not None:
101112 conf.set_default('vault_url', vault_url,
1616 """
1717 import abc
1818 import os
19 import uuid
1920
2021 from oslo_config import cfg
2122 from oslo_context import context
2223 from oslo_utils import uuidutils
2324 from oslotest import base
25 import requests
2426 from testtools import testcase
2527
2628 from castellan.common import exception
108110 base.BaseTestCase):
109111 def get_context(self):
110112 return context.get_admin_context()
113
114
115 TEST_POLICY = '''
116 path "{backend}/*" {{
117 capabilities = ["create", "read", "update", "delete", "list"]
118 }}
119
120 path "sys/internal/ui/mounts/{backend}" {{
121 capabilities = ["read"]
122 }}
123 '''
124
125 AUTH_ENDPOINT = 'v1/sys/auth/{auth_type}'
126 POLICY_ENDPOINT = 'v1/sys/policy/{policy_name}'
127 APPROLE_ENDPOINT = 'v1/auth/approle/role/{role_name}'
128
129
130 class VaultKeyManagerAppRoleTestCase(VaultKeyManagerOSLOContextTestCase):
131
132 def _create_key_manager(self):
133 key_mgr = vault_key_manager.VaultKeyManager(cfg.CONF)
134
135 if ('VAULT_TEST_URL' not in os.environ or
136 'VAULT_TEST_ROOT_TOKEN' not in os.environ):
137 raise testcase.TestSkipped('Missing Vault setup information')
138
139 self.root_token_id = os.environ['VAULT_TEST_ROOT_TOKEN']
140 self.vault_url = os.environ['VAULT_TEST_URL']
141
142 test_uuid = str(uuid.uuid4())
143 vault_policy = 'policy-{}'.format(test_uuid)
144 vault_approle = 'approle-{}'.format(test_uuid)
145
146 self.session = requests.Session()
147 self.session.headers.update({'X-Vault-Token': self.root_token_id})
148
149 self._enable_approle()
150 self._create_policy(vault_policy)
151 self._create_approle(vault_approle, vault_policy)
152
153 key_mgr._approle_role_id, key_mgr._approle_secret_id = (
154 self._retrieve_approle(vault_approle)
155 )
156 key_mgr._vault_url = self.vault_url
157 return key_mgr
158
159 def _enable_approle(self):
160 params = {
161 'type': 'approle'
162 }
163 self.session.post(
164 '{}/{}'.format(
165 self.vault_url,
166 AUTH_ENDPOINT.format(auth_type='approle')
167 ),
168 json=params,
169 )
170
171 def _create_policy(self, vault_policy):
172 params = {
173 'rules': TEST_POLICY.format(backend='secret'),
174 }
175 self.session.put(
176 '{}/{}'.format(
177 self.vault_url,
178 POLICY_ENDPOINT.format(policy_name=vault_policy)
179 ),
180 json=params,
181 )
182
183 def _create_approle(self, vault_approle, vault_policy):
184 params = {
185 'token_ttl': '60s',
186 'token_max_ttl': '60s',
187 'policies': [vault_policy],
188 'bind_secret_id': 'true',
189 'bound_cidr_list': '127.0.0.1/32'
190 }
191 self.session.post(
192 '{}/{}'.format(
193 self.vault_url,
194 APPROLE_ENDPOINT.format(role_name=vault_approle)
195 ),
196 json=params,
197 )
198
199 def _retrieve_approle(self, vault_approle):
200 approle_role_id = (
201 self.session.get(
202 '{}/v1/auth/approle/role/{}/role-id'.format(
203 self.vault_url,
204 vault_approle
205 )).json()['data']['role_id']
206 )
207 approle_secret_id = (
208 self.session.post(
209 '{}/v1/auth/approle/role/{}/secret-id'.format(
210 self.vault_url,
211 vault_approle
212 )).json()['data']['secret_id']
213 )
214 return (approle_role_id, approle_secret_id)
0 ---
1 features:
2 - |
3 Added support for AppRole based authentication to the Vault
4 key manager configured using new approle_role_id and
5 optional approle_secret_id options.
6 (https://www.vaultproject.io/docs/auth/approle.html)