Skip to content

Commit d3d50ea

Browse files
authored
Merge pull request #80 from myyukiho/feature_add-support-for-iam-IAM-342
Add support for IAM authentication
2 parents 2e5d1ae + 37761c7 commit d3d50ea

File tree

4 files changed

+88
-18
lines changed

4 files changed

+88
-18
lines changed

README.rst

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,34 @@ you need apply **access key** on `qingcloud console <https://console.qingcloud.c
4343

4444
QingCloud IaaS API
4545
'''''''''''''''''''
46-
Pass access key id and secret key into method ``connect_to_zone`` to create connection ::
46+
1. Pass access key id and secret key into method ``connect_to_zone`` to create connection ::
4747

48-
>>> import qingcloud.iaas
49-
>>> conn = qingcloud.iaas.connect_to_zone(
50-
'zone id',
51-
'access key id',
52-
'secret access key'
53-
)
48+
>>> import qingcloud.iaas
49+
>>> conn = qingcloud.iaas.connect_to_zone(
50+
'zone id',
51+
'access key id',
52+
'secret access key'
53+
)
5454

55-
The variable ``conn`` is the instance of ``qingcloud.iaas.connection.APIConnection``,
56-
we can use it to call resource related methods.
5755

58-
Example::
56+
2. Call API by using IAM role
57+
58+
If you would like to call our APIs without access key and secret key (bad things would happen if they were lost or leaked)
59+
or if you want a finer access control over your instances, there is a easy way to do it :P
60+
61+
- Go to our IAM service, create an instance role and attach it to your instance.
62+
- Create connection without access key and secret key. ::
63+
64+
>>> import qingcloud.iaas
65+
>>> conn = qingcloud.iaas.connect_to_zone(
66+
'zone id',
67+
None,
68+
None
69+
)
70+
71+
72+
The variable ``conn`` is the instance of ``qingcloud.iaas.connection.APIConnection``,
73+
we can use it to call resource related methods. Example::
5974

6075
# launch instances
6176
>>> ret = conn.run_instances(
@@ -104,3 +119,4 @@ Example::
104119

105120
# Delete the key
106121
>>> bucket.delete_key('myobject')
122+

qingcloud/conn/auth.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,11 @@ def _calc_signature(self, params, verb, path):
112112
def add_auth(self, req, **kwargs):
113113
""" add authorize information for request
114114
"""
115-
req.params['access_key_id'] = self.qy_access_key_id
116-
req.params['signature_version'] = self.SignatureVersion
115+
req.params['access_key_id'] = self.qy_access_key_id if 'access_key' not in kwargs else kwargs.get('access_key')
116+
if 'token' in kwargs:
117+
req.params['token'] = kwargs.get('token')
118+
req.params['signature_version'] = self.SignatureVersion if 'signature_version' not in kwargs \
119+
else kwargs.get('signature_version')
117120
req.params['version'] = self.APIVersion
118121
time_stamp = get_ts()
119122
req.params['time_stamp'] = time_stamp
@@ -135,6 +138,7 @@ def add_auth(self, req, **kwargs):
135138
req.body = ''
136139
# if this is a retried req, the qs from the previous try will
137140
# already be there, we need to get rid of that and rebuild it
141+
req.params["signature"] = signature
138142
req.path = req.path.split('?')[0]
139143
req.path = (req.path + '?' + qs +
140144
'&signature=' + urllib.quote_plus(signature))

qingcloud/conn/connection.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
except:
2222
import http.client as httplib
2323

24+
from qingcloud.misc.json_tool import json_load
25+
from qingcloud.conn.auth import QuerySignatureAuthHandler
26+
2427

2528
class ConnectionQueue(object):
2629
""" Http connection queue
@@ -148,7 +151,11 @@ def __str__(self):
148151

149152
def authorize(self, connection, **kwargs):
150153
# add authorize information to request
151-
if connection._auth_handler:
154+
if connection.iam_access_key:
155+
kwargs.update({'access_key': connection.iam_access_key,
156+
'token': connection._token,
157+
'signature_version': 2})
158+
if connection._auth_handler and (connection.qy_access_key_id or connection.iam_access_key):
152159
connection._auth_handler.add_auth(self, **kwargs)
153160

154161

@@ -180,7 +187,7 @@ class HttpConnection(object):
180187

181188
def __init__(self, qy_access_key_id, qy_secret_access_key, host=None,
182189
port=443, protocol="https", pool=None, expires=None,
183-
http_socket_timeout=10, debug=False):
190+
http_socket_timeout=10, debug=False, credential_proxy_host=None, credential_proxy_port=80):
184191
"""
185192
@param qy_access_key_id - the access key id
186193
@param qy_secret_access_key - the secret access key
@@ -204,6 +211,12 @@ def __init__(self, qy_access_key_id, qy_secret_access_key, host=None,
204211
self._proxy_port = None
205212
self._proxy_headers = None
206213
self._proxy_protocol = None
214+
self._token = ''
215+
self._token_exp = None
216+
self.credential_proxy_host = credential_proxy_host
217+
self.credential_proxy_port = credential_proxy_port
218+
self.iam_access_key = None
219+
self.iam_secret_key = None
207220

208221
def set_proxy(self, host, port=None, headers=None, protocol="http"):
209222
""" set http (https) proxy
@@ -263,6 +276,10 @@ def send(self, method, path, params=None, headers=None, host=None,
263276
if not host:
264277
host = self.host
265278

279+
if not self.qy_access_key_id and not self.qy_secret_access_key:
280+
if self._token:
281+
path = '/iam/'
282+
266283
# Build the http request
267284
request = self.build_http_request(method, path, params, auth_path,
268285
headers, host, data)
@@ -299,3 +316,31 @@ def send(self, method, path, params=None, headers=None, host=None,
299316
self._set_conn(conn)
300317

301318
return response
319+
320+
def _check_token(self):
321+
if not self._token or not self._token_exp or time.time() >= self._token_exp:
322+
try:
323+
conn = httplib.HTTPConnection(self.credential_proxy_host, self.credential_proxy_port, timeout=1)
324+
conn.request("GET", "/latest/meta-data/security-credentials", headers={"Accept": "application/json"})
325+
response = conn.getresponse()
326+
# Reuse the connection
327+
if response.status == 200:
328+
r = response.read()
329+
if r:
330+
# first reverse escape, then json_load
331+
r = json_load(eval(r))
332+
self._token = r.get('id_token')
333+
self._token_exp = r.get('expiration')
334+
self.iam_access_key = r.get('access_key')
335+
self.iam_secret_key = r.get('secret_key')
336+
337+
self._auth_handler = QuerySignatureAuthHandler(self.host,
338+
str(self.iam_access_key),
339+
str(self.iam_secret_key))
340+
341+
elif response.status == 404:
342+
print("The current instance has no credentials")
343+
pass
344+
except Exception as e:
345+
print("Failed to get credentials due to error: %s" % e)
346+
pass

qingcloud/iaas/connection.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ class APIConnection(HttpConnection):
5555
def __init__(self, qy_access_key_id, qy_secret_access_key, zone,
5656
host="api.qingcloud.com", port=443, protocol="https",
5757
pool=None, expires=None,
58-
retry_time=2, http_socket_timeout=60, debug=False):
58+
retry_time=2, http_socket_timeout=60, debug=False,
59+
credential_proxy_host="169.254.169.254", credential_proxy_port=80):
5960
"""
6061
@param qy_access_key_id - the access key id
6162
@param qy_secret_access_key - the secret access key
@@ -73,10 +74,14 @@ def __init__(self, qy_access_key_id, qy_secret_access_key, zone,
7374

7475
super(APIConnection, self).__init__(
7576
qy_access_key_id, qy_secret_access_key, host, port, protocol,
76-
pool, expires, http_socket_timeout, debug)
77+
pool, expires, http_socket_timeout, debug, credential_proxy_host, credential_proxy_port)
7778

78-
self._auth_handler = QuerySignatureAuthHandler(self.host,
79-
self.qy_access_key_id, self.qy_secret_access_key)
79+
if not self.qy_access_key_id and not self.qy_secret_access_key:
80+
self._check_token()
81+
82+
else:
83+
self._auth_handler = QuerySignatureAuthHandler(self.host,
84+
self.qy_access_key_id, self.qy_secret_access_key)
8085

8186
# other apis
8287
self.actions = [

0 commit comments

Comments
 (0)