Skip to content

Commit c2059b3

Browse files
committed
Initial GCP auth backend implementation
1 parent acd8854 commit c2059b3

File tree

5 files changed

+124
-0
lines changed

5 files changed

+124
-0
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ client.auth_aws_iam(credentials.access_key, credentials.secret_key, credentials.
7575
# GitHub
7676
client.auth_github('MY_GITHUB_TOKEN')
7777

78+
# GCP (from GCE instance)
79+
import requests
80+
81+
VAULT_ADDR="https://vault.example.com:8200"
82+
ROLE="example"
83+
AUDIENCE_URL = VAULT_ADDR + "/vault/" + ROLE
84+
METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
85+
FORMAT = 'full'
86+
87+
url = 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience={}&format={}'.format(AUDIENCE_URL, FORMAT)
88+
r = requests.get(url, headers=METADATA_HEADERS)
89+
client.auth_gcp(ROLE, r.text)
90+
7891
# LDAP, Username & Password
7992
client.auth_ldap('MY_USERNAME', 'MY_PASSWORD')
8093
client.auth_userpass('MY_USERNAME', 'MY_PASSWORD')

hvac/tests/test_gcp_methods.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from unittest import TestCase
2+
3+
import requests_mock
4+
from parameterized import parameterized
5+
6+
from hvac import Client
7+
8+
9+
class TestGcpMethods(TestCase):
10+
"""Unit tests providing coverage for GCP auth backend-related methods/routes."""
11+
12+
@parameterized.expand([
13+
("default mount point", "custom_role", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", None),
14+
("custom mount point", "custom_role", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", "gcp-not-default")
15+
])
16+
@requests_mock.Mocker()
17+
def test_auth_gcp(self, test_label, test_role, test_jwt, mount_point, requests_mocker):
18+
mock_response = {
19+
'auth': {
20+
'accessor': 'accessor-1234-5678-9012-345678901234',
21+
'client_token': 'cltoken-1234-5678-9012-345678901234',
22+
'lease_duration': 10000,
23+
'metadata': {
24+
'role': 'custom_role',
25+
'service_account_email': '[email protected]',
26+
'service_account_id': '111111111111111111111'
27+
},
28+
'policies': [
29+
'default',
30+
'custom_role'
31+
],
32+
'renewable': True
33+
},
34+
'data': None,
35+
'lease_duration': 0,
36+
'lease_id': '',
37+
'renewable': False,
38+
'request_id': 'requesti-1234-5678-9012-345678901234',
39+
'warnings': [],
40+
'wrap_info': None
41+
}
42+
mock_url = 'http://localhost:8200/v1/auth/{0}/login'.format(
43+
'gcp' if mount_point is None else mount_point)
44+
requests_mocker.register_uri(
45+
method='POST',
46+
url=mock_url,
47+
json=mock_response
48+
)
49+
client = Client()
50+
51+
if mount_point is None:
52+
actual_response = client.auth_gcp(
53+
role=test_role,
54+
jwt=test_jwt
55+
)
56+
else:
57+
actual_response = client.auth_gcp(
58+
role=test_role,
59+
jwt=test_jwt,
60+
mount_point=mount_point
61+
)
62+
63+
# ensure we received our mock response data back successfully
64+
self.assertEqual(mock_response, actual_response)

hvac/tests/test_integration.py

+28
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,34 @@ def test_auth_ec2_alternate_mount_point_with_no_client_token(self):
12441244
self.client.token = self.root_token()
12451245
self.client.disable_auth_backend(mount_point=test_mount_point)
12461246

1247+
def test_auth_gcp_alternate_mount_point_with_no_client_token_exception(self):
1248+
test_mount_point = 'gcp-custom-path'
1249+
# Turn on the gcp backend with a custom mount_point path specified.
1250+
if '{0}/'.format(test_mount_point) in self.client.list_auth_backends():
1251+
self.client.disable_auth_backend(test_mount_point)
1252+
self.client.enable_auth_backend('gcp', mount_point=test_mount_point)
1253+
1254+
# Drop the client's token to replicate a typical end user's use of any auth method.
1255+
# I.e., its reasonable to expect the method is being called to _retrieve_ a token in the first place.
1256+
self.client.token = None
1257+
1258+
# Load a mock JWT stand in for a real document from GCP.
1259+
with open('test/example.jwt') as fp:
1260+
jwt = fp.read()
1261+
1262+
# When attempting to auth (POST) to an auth backend mounted at a different path than the default, we expect a
1263+
# generic 'missing client token' response from Vault.
1264+
with self.assertRaises(exceptions.InvalidRequest) as assertRaisesContext:
1265+
self.client.auth_gcp('example-role', jwt)
1266+
1267+
expected_exception_message = 'missing client token'
1268+
actual_exception_message = str(assertRaisesContext.exception)
1269+
self.assertEqual(expected_exception_message, actual_exception_message)
1270+
1271+
# Reset test state.
1272+
self.client.token = self.root_token()
1273+
self.client.disable_auth_backend(mount_point=test_mount_point)
1274+
12471275
def test_tune_auth_backend(self):
12481276
test_backend_type = 'approle'
12491277
test_mount_point = 'tune-approle'

hvac/v1/__init__.py

+18
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,24 @@ def auth_ec2(self, pkcs7, nonce=None, role=None, use_token=True, mount_point='aw
691691

692692
return self.auth('/v1/auth/{0}/login'.format(mount_point), json=params, use_token=use_token)
693693

694+
def auth_gcp(self, role, jwt, mount_point='gcp', use_token=True):
695+
"""
696+
POST /auth/<mount point>/login
697+
:param role: str, identifier for the GCP auth backend role being requested
698+
:param jwt: str, JSON Web Token from the GCP metadata service
699+
:param mount_point: str, The "path" the GCP auth backend was mounted on. Vault currently defaults to "gcp".
700+
:param use_token: bool, if True, uses the token in the response received from the auth request to set the "token"
701+
attribute on the current Client class instance.
702+
:return: dict, parsed JSON response from the auth POST request
703+
"""
704+
705+
params = {
706+
'role': role,
707+
'jwt': jwt
708+
}
709+
710+
return self.auth('/v1/auth/{0}/login'.format(mount_point), json=params, use_token=use_token)
711+
694712
def create_userpass(self, username, password, policies, mount_point='userpass', **kwargs):
695713
"""
696714
POST /auth/<mount point>/users/<username>

test/example.jwt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

0 commit comments

Comments
 (0)